mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +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:
parent
78500205df
commit
33421bdd44
@ -52,7 +52,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
|||||||
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
|
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
|
||||||
lateinit var newNode: (CordaX500Name) -> NodeInfo
|
lateinit var newNode: (CordaX500Name) -> NodeInfo
|
||||||
|
|
||||||
override fun setup() = driver {
|
override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
||||||
val cashUser = User("user1", "test", permissions = setOf(
|
val cashUser = User("user1", "test", permissions = setOf(
|
||||||
startFlowPermission<CashIssueFlow>(),
|
startFlowPermission<CashIssueFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlowPermission<CashPaymentFlow>(),
|
||||||
|
@ -3,8 +3,8 @@ package net.corda.confidential
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
@ -22,10 +22,10 @@ object IdentitySyncFlow {
|
|||||||
* @return a mapping of well known identities to the confidential identities used in the transaction.
|
* @return a mapping of well known identities to the confidential identities used in the transaction.
|
||||||
*/
|
*/
|
||||||
// TODO: Can this be triggered automatically from [SendTransactionFlow]
|
// TODO: Can this be triggered automatically from [SendTransactionFlow]
|
||||||
class Send(val otherSides: Set<Party>,
|
class Send(val otherSideSessions: Set<FlowSession>,
|
||||||
val tx: WireTransaction,
|
val tx: WireTransaction,
|
||||||
override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
|
override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
|
||||||
constructor(otherSide: Party, tx: WireTransaction) : this(setOf(otherSide), tx, tracker())
|
constructor(otherSide: FlowSession, tx: WireTransaction) : this(setOf(otherSide), tx, tracker())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
object SYNCING_IDENTITIES : ProgressTracker.Step("Syncing identities")
|
object SYNCING_IDENTITIES : ProgressTracker.Step("Syncing identities")
|
||||||
@ -45,9 +45,9 @@ object IdentitySyncFlow {
|
|||||||
val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = identities
|
val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = identities
|
||||||
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }.toMap()
|
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }.toMap()
|
||||||
|
|
||||||
otherSides.forEach { otherSide ->
|
otherSideSessions.forEach { otherSideSession ->
|
||||||
val requestedIdentities: List<AbstractParty> = sendAndReceive<List<AbstractParty>>(otherSide, confidentialIdentities).unwrap { req ->
|
val requestedIdentities: List<AbstractParty> = otherSideSession.sendAndReceive<List<AbstractParty>>(confidentialIdentities).unwrap { req ->
|
||||||
require(req.all { it in identityCertificates.keys }) { "${otherSide} requested a confidential identity not part of transaction: ${tx.id}" }
|
require(req.all { it in identityCertificates.keys }) { "${otherSideSession.counterparty} requested a confidential identity not part of transaction: ${tx.id}" }
|
||||||
req
|
req
|
||||||
}
|
}
|
||||||
val sendIdentities: List<PartyAndCertificate?> = requestedIdentities.map {
|
val sendIdentities: List<PartyAndCertificate?> = requestedIdentities.map {
|
||||||
@ -57,7 +57,7 @@ object IdentitySyncFlow {
|
|||||||
else
|
else
|
||||||
throw IllegalStateException("Counterparty requested a confidential identity for which we do not have the certificate path: ${tx.id}")
|
throw IllegalStateException("Counterparty requested a confidential identity for which we do not have the certificate path: ${tx.id}")
|
||||||
}
|
}
|
||||||
send(otherSide, sendIdentities)
|
otherSideSession.send(sendIdentities)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ object IdentitySyncFlow {
|
|||||||
* Handle an offer to provide proof of identity (in the form of certificate paths) for confidential identities which
|
* Handle an offer to provide proof of identity (in the form of certificate paths) for confidential identities which
|
||||||
* we do not yet know about.
|
* we do not yet know about.
|
||||||
*/
|
*/
|
||||||
class Receive(val otherSide: Party) : FlowLogic<Unit>() {
|
class Receive(val otherSideSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
companion object {
|
companion object {
|
||||||
object RECEIVING_IDENTITIES : ProgressTracker.Step("Receiving confidential identities")
|
object RECEIVING_IDENTITIES : ProgressTracker.Step("Receiving confidential identities")
|
||||||
object RECEIVING_CERTIFICATES : ProgressTracker.Step("Receiving certificates for unknown identities")
|
object RECEIVING_CERTIFICATES : ProgressTracker.Step("Receiving certificates for unknown identities")
|
||||||
@ -78,10 +78,10 @@ object IdentitySyncFlow {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Unit {
|
override fun call(): Unit {
|
||||||
progressTracker.currentStep = RECEIVING_IDENTITIES
|
progressTracker.currentStep = RECEIVING_IDENTITIES
|
||||||
val allIdentities = receive<List<AbstractParty>>(otherSide).unwrap { it }
|
val allIdentities = otherSideSession.receive<List<AbstractParty>>().unwrap { it }
|
||||||
val unknownIdentities = allIdentities.filter { serviceHub.identityService.partyFromAnonymous(it) == null }
|
val unknownIdentities = allIdentities.filter { serviceHub.identityService.partyFromAnonymous(it) == null }
|
||||||
progressTracker.currentStep = RECEIVING_CERTIFICATES
|
progressTracker.currentStep = RECEIVING_CERTIFICATES
|
||||||
val missingIdentities = sendAndReceive<List<PartyAndCertificate>>(otherSide, unknownIdentities)
|
val missingIdentities = otherSideSession.sendAndReceive<List<PartyAndCertificate>>(unknownIdentities)
|
||||||
|
|
||||||
// Batch verify the identities we've received, so we know they're all correct before we start storing them in
|
// Batch verify the identities we've received, so we know they're all correct before we start storing them in
|
||||||
// the identity service
|
// the identity service
|
||||||
|
@ -18,10 +18,10 @@ import net.corda.core.utilities.unwrap
|
|||||||
*/
|
*/
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class SwapIdentitiesFlow(private val otherSide: Party,
|
class SwapIdentitiesFlow(private val otherParty: Party,
|
||||||
private val revocationEnabled: Boolean,
|
private val revocationEnabled: Boolean,
|
||||||
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousParty>>() {
|
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousParty>>() {
|
||||||
constructor(otherSide: Party) : this(otherSide, false, tracker())
|
constructor(otherParty: Party) : this(otherParty, false, tracker())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
|
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
|
||||||
@ -43,14 +43,15 @@ class SwapIdentitiesFlow(private val otherSide: Party,
|
|||||||
|
|
||||||
// Special case that if we're both parties, a single identity is generated
|
// Special case that if we're both parties, a single identity is generated
|
||||||
val identities = LinkedHashMap<Party, AnonymousParty>()
|
val identities = LinkedHashMap<Party, AnonymousParty>()
|
||||||
if (serviceHub.myInfo.isLegalIdentity(otherSide)) {
|
if (serviceHub.myInfo.isLegalIdentity(otherParty)) {
|
||||||
identities.put(otherSide, legalIdentityAnonymous.party.anonymise())
|
identities.put(otherParty, legalIdentityAnonymous.party.anonymise())
|
||||||
} else {
|
} else {
|
||||||
val anonymousOtherSide = sendAndReceive<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
|
val otherSession = initiateFlow(otherParty)
|
||||||
validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
|
val anonymousOtherSide = otherSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity ->
|
||||||
|
validateAndRegisterIdentity(serviceHub.identityService, otherSession.counterparty, confidentialIdentity)
|
||||||
}
|
}
|
||||||
identities.put(ourIdentity, legalIdentityAnonymous.party.anonymise())
|
identities.put(ourIdentity, legalIdentityAnonymous.party.anonymise())
|
||||||
identities.put(otherSide, anonymousOtherSide.party.anonymise())
|
identities.put(otherSession.counterparty, anonymousOtherSide.party.anonymise())
|
||||||
}
|
}
|
||||||
return identities
|
return identities
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,14 @@ package net.corda.confidential
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
|
|
||||||
class SwapIdentitiesHandler(val otherSide: Party, val revocationEnabled: Boolean) : FlowLogic<Unit>() {
|
class SwapIdentitiesHandler(val otherSideSession: FlowSession, val revocationEnabled: Boolean) : FlowLogic<Unit>() {
|
||||||
constructor(otherSide: Party) : this(otherSide, false)
|
constructor(otherSideSession: FlowSession) : this(otherSideSession, false)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
object SENDING_KEY : ProgressTracker.Step("Sending key")
|
object SENDING_KEY : ProgressTracker.Step("Sending key")
|
||||||
}
|
}
|
||||||
@ -20,8 +21,8 @@ class SwapIdentitiesHandler(val otherSide: Party, val revocationEnabled: Boolean
|
|||||||
val revocationEnabled = false
|
val revocationEnabled = false
|
||||||
progressTracker.currentStep = SENDING_KEY
|
progressTracker.currentStep = SENDING_KEY
|
||||||
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
|
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
|
||||||
sendAndReceive<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
|
otherSideSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity ->
|
||||||
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
|
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, confidentialIdentity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package net.corda.confidential
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
@ -12,11 +13,7 @@ import net.corda.core.utilities.unwrap
|
|||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.*
|
||||||
import net.corda.testing.BOB
|
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.getDefaultNotary
|
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -75,19 +72,20 @@ class IdentitySyncFlowTests {
|
|||||||
class Initiator(val otherSide: Party, val tx: WireTransaction): FlowLogic<Boolean>() {
|
class Initiator(val otherSide: Party, val tx: WireTransaction): FlowLogic<Boolean>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Boolean {
|
override fun call(): Boolean {
|
||||||
subFlow(IdentitySyncFlow.Send(otherSide, tx))
|
val session = initiateFlow(otherSide)
|
||||||
|
subFlow(IdentitySyncFlow.Send(session, tx))
|
||||||
// Wait for the counterparty to indicate they're done
|
// Wait for the counterparty to indicate they're done
|
||||||
return receive<Boolean>(otherSide).unwrap { it }
|
return session.receive<Boolean>().unwrap { it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(IdentitySyncFlowTests.Initiator::class)
|
@InitiatedBy(IdentitySyncFlowTests.Initiator::class)
|
||||||
class Receive(val otherSide: Party): FlowLogic<Unit>() {
|
class Receive(val otherSideSession: FlowSession): FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
subFlow(IdentitySyncFlow.Receive(otherSide))
|
subFlow(IdentitySyncFlow.Receive(otherSideSession))
|
||||||
// Notify the initiator that we've finished syncing
|
// Notify the initiator that we've finished syncing
|
||||||
send(otherSide, true)
|
otherSideSession.send(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,13 +6,12 @@ import net.corda.core.contracts.StateAndRef
|
|||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import java.security.PublicKey
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract flow to be used for replacing one state with another, for example when changing the notary of a state.
|
* Abstract flow to be used for replacing one state with another, for example when changing the notary of a state.
|
||||||
@ -33,10 +32,8 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
* The assembled transaction for upgrading a contract.
|
* The assembled transaction for upgrading a contract.
|
||||||
*
|
*
|
||||||
* @param stx signed transaction to do the upgrade.
|
* @param stx signed transaction to do the upgrade.
|
||||||
* @param participants the parties involved in the upgrade transaction.
|
|
||||||
* @param myKey key
|
|
||||||
*/
|
*/
|
||||||
data class UpgradeTx(val stx: SignedTransaction, val participants: Iterable<PublicKey>, val myKey: PublicKey)
|
data class UpgradeTx(val stx: SignedTransaction)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [Instigator] assembles the transaction for state replacement and sends out change proposals to all participants
|
* The [Instigator] assembles the transaction for state replacement and sends out change proposals to all participants
|
||||||
@ -62,15 +59,11 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
@Throws(StateReplacementException::class)
|
@Throws(StateReplacementException::class)
|
||||||
override fun call(): StateAndRef<T> {
|
override fun call(): StateAndRef<T> {
|
||||||
val (stx, participantKeys, myKey) = assembleTx()
|
val (stx) = assembleTx()
|
||||||
|
val participantSessions = getParticipantSessions()
|
||||||
progressTracker.currentStep = SIGNING
|
progressTracker.currentStep = SIGNING
|
||||||
|
|
||||||
val signatures = if (participantKeys.singleOrNull() == myKey) {
|
val signatures = collectSignatures(participantSessions, stx)
|
||||||
getNotarySignatures(stx)
|
|
||||||
} else {
|
|
||||||
collectSignatures(participantKeys - myKey, stx)
|
|
||||||
}
|
|
||||||
|
|
||||||
val finalTx = stx + signatures
|
val finalTx = stx + signatures
|
||||||
serviceHub.recordTransactions(finalTx)
|
serviceHub.recordTransactions(finalTx)
|
||||||
@ -89,35 +82,38 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
/**
|
/**
|
||||||
* Build the upgrade transaction.
|
* Build the upgrade transaction.
|
||||||
*
|
*
|
||||||
* @return a triple of the transaction, the public keys of all participants, and the participating public key of
|
* @return the transaction
|
||||||
* this node.
|
|
||||||
*/
|
*/
|
||||||
abstract protected fun assembleTx(): UpgradeTx
|
abstract protected fun assembleTx(): UpgradeTx
|
||||||
|
|
||||||
@Suspendable
|
/**
|
||||||
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<TransactionSignature> {
|
* Initiate sessions with parties we want signatures from.
|
||||||
// In identity service we record all identities we know about from network map.
|
*/
|
||||||
val parties = participants.map {
|
open fun getParticipantSessions(): List<Pair<FlowSession, List<AbstractParty>>> {
|
||||||
serviceHub.identityService.partyFromKey(it) ?:
|
return serviceHub.excludeMe(serviceHub.groupAbstractPartyByWellKnownParty(originalState.state.data.participants)).map { initiateFlow(it.key) to it.value }
|
||||||
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val participantSignatures = parties.map { getParticipantSignature(it, stx) }
|
@Suspendable
|
||||||
|
private fun collectSignatures(sessions: List<Pair<FlowSession, List<AbstractParty>>>, stx: SignedTransaction): List<TransactionSignature> {
|
||||||
|
val participantSignatures = sessions.map { getParticipantSignature(it.first, it.second, stx) }
|
||||||
|
|
||||||
val allPartySignedTx = stx + participantSignatures
|
val allPartySignedTx = stx + participantSignatures
|
||||||
|
|
||||||
val allSignatures = participantSignatures + getNotarySignatures(allPartySignedTx)
|
val allSignatures = participantSignatures + getNotarySignatures(allPartySignedTx)
|
||||||
parties.forEach { send(it, allSignatures) }
|
sessions.forEach { it.first.send(allSignatures) }
|
||||||
|
|
||||||
return allSignatures
|
return allSignatures
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun getParticipantSignature(party: Party, stx: SignedTransaction): TransactionSignature {
|
private fun getParticipantSignature(session: FlowSession, party: List<AbstractParty>, stx: SignedTransaction): TransactionSignature {
|
||||||
|
require(party.size == 1) {
|
||||||
|
"We do not currently support multiple signatures from the same party ${session.counterparty}: $party"
|
||||||
|
}
|
||||||
val proposal = Proposal(originalState.ref, modification)
|
val proposal = Proposal(originalState.ref, modification)
|
||||||
subFlow(SendTransactionFlow(party, stx))
|
subFlow(SendTransactionFlow(session, stx))
|
||||||
return sendAndReceive<TransactionSignature>(party, proposal).unwrap {
|
return session.sendAndReceive<TransactionSignature>(proposal).unwrap {
|
||||||
check(party.owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" }
|
check(party.single().owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" }
|
||||||
it.verify(stx.id)
|
it.verify(stx.id)
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
@ -136,9 +132,9 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
|
|
||||||
// Type parameter should ideally be Unit but that prevents Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964).
|
// Type parameter should ideally be Unit but that prevents Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964).
|
||||||
// We use Void? instead of Unit? as that's what you'd use in Java.
|
// We use Void? instead of Unit? as that's what you'd use in Java.
|
||||||
abstract class Acceptor<in T>(val otherSide: Party,
|
abstract class Acceptor<in T>(val initiatingSession: FlowSession,
|
||||||
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
|
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
|
||||||
constructor(otherSide: Party) : this(otherSide, Acceptor.tracker())
|
constructor(initiatingSession: FlowSession) : this(initiatingSession, Acceptor.tracker())
|
||||||
companion object {
|
companion object {
|
||||||
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
||||||
object APPROVING : ProgressTracker.Step("State replacement approved")
|
object APPROVING : ProgressTracker.Step("State replacement approved")
|
||||||
@ -151,9 +147,9 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
override fun call(): Void? {
|
override fun call(): Void? {
|
||||||
progressTracker.currentStep = VERIFYING
|
progressTracker.currentStep = VERIFYING
|
||||||
// We expect stx to have insufficient signatures here
|
// We expect stx to have insufficient signatures here
|
||||||
val stx = subFlow(ReceiveTransactionFlow(otherSide, checkSufficientSignatures = false))
|
val stx = subFlow(ReceiveTransactionFlow(initiatingSession, checkSufficientSignatures = false))
|
||||||
checkMySignatureRequired(stx)
|
checkMySignatureRequired(stx)
|
||||||
val maybeProposal: UntrustworthyData<Proposal<T>> = receive(otherSide)
|
val maybeProposal: UntrustworthyData<Proposal<T>> = initiatingSession.receive()
|
||||||
maybeProposal.unwrap {
|
maybeProposal.unwrap {
|
||||||
verifyProposal(stx, it)
|
verifyProposal(stx, it)
|
||||||
}
|
}
|
||||||
@ -166,7 +162,7 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
progressTracker.currentStep = APPROVING
|
progressTracker.currentStep = APPROVING
|
||||||
|
|
||||||
val mySignature = sign(stx)
|
val mySignature = sign(stx)
|
||||||
val swapSignatures = sendAndReceive<List<TransactionSignature>>(otherSide, mySignature)
|
val swapSignatures = initiatingSession.sendAndReceive<List<TransactionSignature>>(mySignature)
|
||||||
|
|
||||||
// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.
|
// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.
|
||||||
val allSignatures = swapSignatures.unwrap { signatures ->
|
val allSignatures = swapSignatures.unwrap { signatures ->
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
package net.corda.core.flows
|
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.core.utilities.NonEmptySet
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the specified parties about a transaction. The remote peers will download this transaction and its
|
|
||||||
* dependency graph, verifying them all. The flow returns when all peers have acknowledged the transactions
|
|
||||||
* as valid. Normally you wouldn't use this directly, it would be called via [FinalityFlow].
|
|
||||||
*
|
|
||||||
* @param notarisedTransaction transaction which has been notarised (if needed) and is ready to notify nodes about.
|
|
||||||
* @param participants a list of participants involved in the transaction.
|
|
||||||
* @return a list of participants who were successfully notified of the transaction.
|
|
||||||
*/
|
|
||||||
@InitiatingFlow
|
|
||||||
class BroadcastTransactionFlow(val notarisedTransaction: SignedTransaction,
|
|
||||||
val participants: NonEmptySet<Party>) : FlowLogic<Unit>() {
|
|
||||||
@Suspendable
|
|
||||||
override fun call() {
|
|
||||||
// TODO: Messaging layer should handle this broadcast for us
|
|
||||||
participants.filter { !serviceHub.myInfo.isLegalIdentity(it) }.forEach { participant ->
|
|
||||||
// SendTransactionFlow allows otherParty to access our data to resolve the transaction.
|
|
||||||
subFlow(SendTransactionFlow(participant, notarisedTransaction))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,8 +3,6 @@ package net.corda.core.flows
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
import net.corda.core.utilities.toBase58String
|
|
||||||
import net.corda.core.identity.AnonymousParty
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -57,21 +55,25 @@ import java.security.PublicKey
|
|||||||
* val stx = subFlow(CollectSignaturesFlow(ptx))
|
* val stx = subFlow(CollectSignaturesFlow(ptx))
|
||||||
*
|
*
|
||||||
* @param partiallySignedTx Transaction to collect the remaining signatures for
|
* @param partiallySignedTx Transaction to collect the remaining signatures for
|
||||||
|
* @param sessionsToCollectFrom A session for every party we need to collect a signature from. Must be an exact match.
|
||||||
* @param myOptionalKeys set of keys in the transaction which are owned by this node. This includes keys used on commands, not
|
* @param myOptionalKeys set of keys in the transaction which are owned by this node. This includes keys used on commands, not
|
||||||
* just in the states. If null, the default well known identity of the node is used.
|
* just in the states. If null, the default well known identity of the node is used.
|
||||||
*/
|
*/
|
||||||
// TODO: AbstractStateReplacementFlow needs updating to use this flow.
|
// TODO: AbstractStateReplacementFlow needs updating to use this flow.
|
||||||
class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: SignedTransaction,
|
class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: SignedTransaction,
|
||||||
|
val sessionsToCollectFrom: Collection<FlowSession>,
|
||||||
val myOptionalKeys: Iterable<PublicKey>?,
|
val myOptionalKeys: Iterable<PublicKey>?,
|
||||||
override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : FlowLogic<SignedTransaction>() {
|
override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : FlowLogic<SignedTransaction>() {
|
||||||
@JvmOverloads constructor(partiallySignedTx: SignedTransaction, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, null, progressTracker)
|
@JvmOverloads constructor(partiallySignedTx: SignedTransaction, sessionsToCollectFrom: Collection<FlowSession>, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, sessionsToCollectFrom, null, progressTracker)
|
||||||
companion object {
|
companion object {
|
||||||
object COLLECTING : ProgressTracker.Step("Collecting signatures from counter-parties.")
|
object COLLECTING : ProgressTracker.Step("Collecting signatures from counter-parties.")
|
||||||
object VERIFYING : ProgressTracker.Step("Verifying collected signatures.")
|
object VERIFYING : ProgressTracker.Step("Verifying collected signatures.")
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
fun tracker() = ProgressTracker(COLLECTING, VERIFYING)
|
fun tracker() = ProgressTracker(COLLECTING, VERIFYING)
|
||||||
|
|
||||||
// TODO: Make the progress tracker adapt to the number of counter-parties to collect from.
|
// TODO: Make the progress tracker adapt to the number of counter-parties to collect from.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable override fun call(): SignedTransaction {
|
@Suspendable override fun call(): SignedTransaction {
|
||||||
@ -100,8 +102,15 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
|
|||||||
// If the unsigned counter-parties list is empty then we don't need to collect any more signatures here.
|
// If the unsigned counter-parties list is empty then we don't need to collect any more signatures here.
|
||||||
if (unsigned.isEmpty()) return partiallySignedTx
|
if (unsigned.isEmpty()) return partiallySignedTx
|
||||||
|
|
||||||
|
val partyToKeysMap = serviceHub.groupPublicKeysByWellKnownParty(unsigned)
|
||||||
|
// Check that we have a session for all parties. No more, no less.
|
||||||
|
require(sessionsToCollectFrom.map { it.counterparty }.toSet() == partyToKeysMap.keys) {
|
||||||
|
"The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction."
|
||||||
|
}
|
||||||
// Collect signatures from all counter-parties and append them to the partially signed transaction.
|
// Collect signatures from all counter-parties and append them to the partially signed transaction.
|
||||||
val counterpartySignatures = keysToParties(unsigned).map { collectSignature(it.first, it.second) }
|
val counterpartySignatures = sessionsToCollectFrom.flatMap { session ->
|
||||||
|
subFlow(CollectSignatureFlow(partiallySignedTx, session, partyToKeysMap[session.counterparty]!!))
|
||||||
|
}
|
||||||
val stx = partiallySignedTx + counterpartySignatures
|
val stx = partiallySignedTx + counterpartySignatures
|
||||||
|
|
||||||
// Verify all but the notary's signature if the transaction requires a notary, otherwise verify all signatures.
|
// Verify all but the notary's signature if the transaction requires a notary, otherwise verify all signatures.
|
||||||
@ -110,40 +119,38 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
|
|||||||
|
|
||||||
return stx
|
return stx
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// DOCSTART 1
|
||||||
* Lookup the [Party] object for each [PublicKey] using the [ServiceHub.identityService].
|
/**
|
||||||
*
|
* Get and check the required signature.
|
||||||
* @return a pair of the well known identity to contact for a signature, and the public key that party should sign
|
*
|
||||||
* with (this may belong to a confidential identity).
|
* @param partiallySignedTx the transaction to sign.
|
||||||
*/
|
* @param session the [FlowSession] to connect to to get the signature.
|
||||||
@Suspendable private fun keysToParties(keys: Collection<PublicKey>): List<Pair<Party, PublicKey>> = keys.map {
|
* @param signingKeys the list of keys the party should use to sign the transaction.
|
||||||
val party = serviceHub.identityService.partyFromAnonymous(AnonymousParty(it))
|
*/
|
||||||
?: throw IllegalStateException("Party ${it.toBase58String()} not found on the network.")
|
@Suspendable
|
||||||
Pair(party, it)
|
class CollectSignatureFlow(val partiallySignedTx: SignedTransaction, val session: FlowSession, val signingKeys: List<PublicKey>) : FlowLogic<List<TransactionSignature>>() {
|
||||||
}
|
constructor(partiallySignedTx: SignedTransaction, session: FlowSession, vararg signingKeys: PublicKey) :
|
||||||
|
this(partiallySignedTx, session, listOf(*signingKeys))
|
||||||
// DOCSTART 1
|
@Suspendable
|
||||||
/**
|
override fun call(): List<TransactionSignature> {
|
||||||
* Get and check the required signature.
|
|
||||||
*
|
|
||||||
* @param counterparty the party to request a signature from.
|
|
||||||
* @param signingKey the key the party should use to sign the transaction.
|
|
||||||
*/
|
|
||||||
@Suspendable private fun collectSignature(counterparty: Party, signingKey: PublicKey): TransactionSignature {
|
|
||||||
// SendTransactionFlow allows counterparty to access our data to resolve the transaction.
|
// SendTransactionFlow allows counterparty to access our data to resolve the transaction.
|
||||||
subFlow(SendTransactionFlow(counterparty, partiallySignedTx))
|
subFlow(SendTransactionFlow(session, partiallySignedTx))
|
||||||
// Send the key we expect the counterparty to sign with - this is important where they may have several
|
// Send the key we expect the counterparty to sign with - this is important where they may have several
|
||||||
// keys to sign with, as it makes it faster for them to identify the key to sign with, and more straight forward
|
// keys to sign with, as it makes it faster for them to identify the key to sign with, and more straight forward
|
||||||
// for us to check we have the expected signature returned.
|
// for us to check we have the expected signature returned.
|
||||||
send(counterparty, signingKey)
|
session.send(signingKeys)
|
||||||
return receive<TransactionSignature>(counterparty).unwrap {
|
return session.receive<List<TransactionSignature>>().unwrap { signatures ->
|
||||||
require(signingKey.isFulfilledBy(it.by)) { "Not signed by the required signing key." }
|
require(signatures.size == signingKeys.size) { "Need signature for each signing key" }
|
||||||
it
|
signatures.forEachIndexed { index, signature ->
|
||||||
|
require(signingKeys[index].isFulfilledBy(signature.by)) { "Not signed by the required signing key." }
|
||||||
|
}
|
||||||
|
signatures
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// DOCEND 1
|
|
||||||
}
|
}
|
||||||
|
// DOCEND 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [SignTransactionFlow] should be called in response to the [CollectSignaturesFlow]. It automates the signing of
|
* The [SignTransactionFlow] should be called in response to the [CollectSignaturesFlow]. It automates the signing of
|
||||||
@ -159,15 +166,15 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
|
|||||||
* - Subclass [SignTransactionFlow] - this can be done inside an existing flow (as shown below)
|
* - Subclass [SignTransactionFlow] - this can be done inside an existing flow (as shown below)
|
||||||
* - Override the [checkTransaction] method to add some custom verification logic
|
* - Override the [checkTransaction] method to add some custom verification logic
|
||||||
* - Call the flow via [FlowLogic.subFlow]
|
* - Call the flow via [FlowLogic.subFlow]
|
||||||
* - The flow returns the fully signed transaction once it has been committed to the ledger
|
* - The flow returns the transaction signed with the additional signature.
|
||||||
*
|
*
|
||||||
* Example - checking and signing a transaction involving a [net.corda.core.contracts.DummyContract], see
|
* Example - checking and signing a transaction involving a [net.corda.core.contracts.DummyContract], see
|
||||||
* CollectSignaturesFlowTests.kt for further examples:
|
* CollectSignaturesFlowTests.kt for further examples:
|
||||||
*
|
*
|
||||||
* class Responder(val otherParty: Party): FlowLogic<SignedTransaction>() {
|
* class Responder(val otherPartySession: FlowSession): FlowLogic<SignedTransaction>() {
|
||||||
* @Suspendable override fun call(): SignedTransaction {
|
* @Suspendable override fun call(): SignedTransaction {
|
||||||
* // [SignTransactionFlow] sub-classed as a singleton object.
|
* // [SignTransactionFlow] sub-classed as a singleton object.
|
||||||
* val flow = object : SignTransactionFlow(otherParty) {
|
* val flow = object : SignTransactionFlow(otherPartySession) {
|
||||||
* @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
* @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
||||||
* val tx = stx.tx
|
* val tx = stx.tx
|
||||||
* val magicNumberState = tx.outputs.single().data as DummyContract.MultiOwnerState
|
* val magicNumberState = tx.outputs.single().data as DummyContract.MultiOwnerState
|
||||||
@ -182,9 +189,9 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
|
|||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param otherParty The counter-party which is providing you a transaction to sign.
|
* @param otherSideSession The session which is providing you a transaction to sign.
|
||||||
*/
|
*/
|
||||||
abstract class SignTransactionFlow(val otherParty: Party,
|
abstract class SignTransactionFlow(val otherSideSession: FlowSession,
|
||||||
override val progressTracker: ProgressTracker = SignTransactionFlow.tracker()) : FlowLogic<SignedTransaction>() {
|
override val progressTracker: ProgressTracker = SignTransactionFlow.tracker()) : FlowLogic<SignedTransaction>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -192,23 +199,24 @@ abstract class SignTransactionFlow(val otherParty: Party,
|
|||||||
object VERIFYING : ProgressTracker.Step("Verifying transaction proposal.")
|
object VERIFYING : ProgressTracker.Step("Verifying transaction proposal.")
|
||||||
object SIGNING : ProgressTracker.Step("Signing transaction proposal.")
|
object SIGNING : ProgressTracker.Step("Signing transaction proposal.")
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING)
|
fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable override fun call(): SignedTransaction {
|
@Suspendable override fun call(): SignedTransaction {
|
||||||
progressTracker.currentStep = RECEIVING
|
progressTracker.currentStep = RECEIVING
|
||||||
// Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures.
|
// Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures.
|
||||||
val stx = subFlow(ReceiveTransactionFlow(otherParty, checkSufficientSignatures = false))
|
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
|
||||||
// Receive the signing key that the party requesting the signature expects us to sign with. Having this provided
|
// Receive the signing key that the party requesting the signature expects us to sign with. Having this provided
|
||||||
// means we only have to check we own that one key, rather than matching all keys in the transaction against all
|
// means we only have to check we own that one key, rather than matching all keys in the transaction against all
|
||||||
// keys we own.
|
// keys we own.
|
||||||
val signingKey = receive<PublicKey>(otherParty).unwrap {
|
val signingKeys = otherSideSession.receive<List<PublicKey>>().unwrap { keys ->
|
||||||
// TODO: We should have a faster way of verifying we own a single key
|
// TODO: We should have a faster way of verifying we own a single key
|
||||||
serviceHub.keyManagementService.filterMyKeys(listOf(it)).single()
|
serviceHub.keyManagementService.filterMyKeys(keys)
|
||||||
}
|
}
|
||||||
progressTracker.currentStep = VERIFYING
|
progressTracker.currentStep = VERIFYING
|
||||||
// Check that the Responder actually needs to sign.
|
// Check that the Responder actually needs to sign.
|
||||||
checkMySignatureRequired(stx, signingKey)
|
checkMySignaturesRequired(stx, signingKeys)
|
||||||
// Check the signatures which have already been provided. Usually the Initiators and possibly an Oracle's.
|
// Check the signatures which have already been provided. Usually the Initiators and possibly an Oracle's.
|
||||||
checkSignatures(stx)
|
checkSignatures(stx)
|
||||||
stx.tx.toLedgerTransaction(serviceHub).verify()
|
stx.tx.toLedgerTransaction(serviceHub).verify()
|
||||||
@ -223,18 +231,19 @@ abstract class SignTransactionFlow(val otherParty: Party,
|
|||||||
}
|
}
|
||||||
// Sign and send back our signature to the Initiator.
|
// Sign and send back our signature to the Initiator.
|
||||||
progressTracker.currentStep = SIGNING
|
progressTracker.currentStep = SIGNING
|
||||||
val mySignature = serviceHub.createSignature(stx, signingKey)
|
val mySignatures = signingKeys.map { key ->
|
||||||
send(otherParty, mySignature)
|
serviceHub.createSignature(stx, key)
|
||||||
|
}
|
||||||
|
otherSideSession.send(mySignatures)
|
||||||
|
|
||||||
// Return the fully signed transaction once it has been committed.
|
// Return the additionally signed transaction.
|
||||||
return waitForLedgerCommit(stx.id)
|
return stx + mySignatures
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable private fun checkSignatures(stx: SignedTransaction) {
|
@Suspendable private fun checkSignatures(stx: SignedTransaction) {
|
||||||
val signingIdentities = stx.sigs.map(TransactionSignature::by).mapNotNull(serviceHub.identityService::partyFromKey)
|
val signingWellKnownIdentities = serviceHub.groupPublicKeysByWellKnownParty(stx.sigs.map(TransactionSignature::by))
|
||||||
val signingWellKnownIdentities = signingIdentities.mapNotNull(serviceHub.identityService::partyFromAnonymous)
|
require(otherSideSession.counterparty in signingWellKnownIdentities) {
|
||||||
require(otherParty in signingWellKnownIdentities) {
|
"The Initiator of CollectSignaturesFlow must have signed the transaction. Found ${signingWellKnownIdentities}, expected ${otherSideSession}"
|
||||||
"The Initiator of CollectSignaturesFlow must have signed the transaction. Found ${signingWellKnownIdentities}, expected ${otherParty}"
|
|
||||||
}
|
}
|
||||||
val signed = stx.sigs.map { it.by }
|
val signed = stx.sigs.map { it.by }
|
||||||
val allSigners = stx.tx.requiredSigningKeys
|
val allSigners = stx.tx.requiredSigningKeys
|
||||||
@ -266,9 +275,9 @@ abstract class SignTransactionFlow(val otherParty: Party,
|
|||||||
@Throws(FlowException::class)
|
@Throws(FlowException::class)
|
||||||
abstract protected fun checkTransaction(stx: SignedTransaction)
|
abstract protected fun checkTransaction(stx: SignedTransaction)
|
||||||
|
|
||||||
@Suspendable private fun checkMySignatureRequired(stx: SignedTransaction, signingKey: PublicKey) {
|
@Suspendable private fun checkMySignaturesRequired(stx: SignedTransaction, signingKeys: Iterable<PublicKey>) {
|
||||||
require(signingKey in stx.tx.requiredSigningKeys) {
|
require(signingKeys.all { it in stx.tx.requiredSigningKeys }) {
|
||||||
"Party is not a participant for any of the input states of transaction ${stx.id}"
|
"A signature was requested for a key that isn't part of the required signing keys for transaction ${stx.id}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.core.flows
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -87,13 +86,13 @@ object ContractUpgradeFlow {
|
|||||||
// TODO: We need a much faster way of finding our key in the transaction
|
// TODO: We need a much faster way of finding our key in the transaction
|
||||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||||
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
||||||
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
return AbstractStateReplacementFlow.UpgradeTx(stx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
@InitiatedBy(ContractUpgradeFlow.Initiator::class)
|
@InitiatedBy(ContractUpgradeFlow.Initiator::class)
|
||||||
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
|
class Acceptor(otherSide: FlowSession) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -133,7 +132,7 @@ object ContractUpgradeFlow {
|
|||||||
val proposedTx = stx.tx
|
val proposedTx = stx.tx
|
||||||
val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
||||||
requireThat {
|
requireThat {
|
||||||
"The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
|
"The instigator is one of the participants" using (initiatingSession.counterparty in oldStateAndRef.state.data.participants)
|
||||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
||||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,35 @@
|
|||||||
package net.corda.core.flows
|
package net.corda.core.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.ContractState
|
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.contracts.TransactionState
|
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
import net.corda.core.identity.AbstractParty
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.ResolveTransactionsFlow
|
|
||||||
import net.corda.core.node.ServiceHub
|
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NonEmptySet
|
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.toNonEmptySet
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the given transactions, then sends them to the named notary. If the notary agrees that the transactions
|
* Verifies the given transaction, then sends it to the named notary. If the notary agrees that the transaction
|
||||||
* are acceptable then they are from that point onwards committed to the ledger, and will be written through to the
|
* is acceptable then it is from that point onwards committed to the ledger, and will be written through to the
|
||||||
* vault. Additionally they will be distributed to the parties reflected in the participants list of the states.
|
* vault. Additionally it will be distributed to the parties reflected in the participants list of the states.
|
||||||
*
|
*
|
||||||
* The transactions will be topologically sorted before commitment to ensure that dependencies are committed before
|
* The transaction is expected to have already been resolved: if its dependencies are not available in local
|
||||||
* dependers, so you don't need to do this yourself.
|
* storage, verification will fail. It must have signatures from all necessary parties other than the notary.
|
||||||
*
|
*
|
||||||
* The transactions are expected to have already been resolved: if their dependencies are not available in local
|
* If specified, the extra recipients are sent the given transaction. The base set of parties to inform are calculated
|
||||||
* storage or within the given set, verification will fail. They must have signatures from all necessary parties
|
* from the contract-given set of participants.
|
||||||
* other than the notary.
|
|
||||||
*
|
*
|
||||||
* If specified, the extra recipients are sent all the given transactions. The base set of parties to inform of each
|
* The flow returns the same transaction but with the additional signatures from the notary.
|
||||||
* transaction are calculated on a per transaction basis from the contract-given set of participants.
|
|
||||||
*
|
*
|
||||||
* The flow returns the same transactions, in the same order, with the additional signatures.
|
* @param transaction What to commit.
|
||||||
*
|
|
||||||
* @param transactions What to commit.
|
|
||||||
* @param extraRecipients A list of additional participants to inform of the transaction.
|
* @param extraRecipients A list of additional participants to inform of the transaction.
|
||||||
*/
|
*/
|
||||||
open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
|
@InitiatingFlow
|
||||||
|
class FinalityFlow(val transaction: SignedTransaction,
|
||||||
private val extraRecipients: Set<Party>,
|
private val extraRecipients: Set<Party>,
|
||||||
override val progressTracker: ProgressTracker) : FlowLogic<List<SignedTransaction>>() {
|
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
|
||||||
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(listOf(transaction), extraParticipants, tracker())
|
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(transaction, extraParticipants, tracker())
|
||||||
constructor(transaction: SignedTransaction) : this(listOf(transaction), emptySet(), tracker())
|
constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker())
|
||||||
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(listOf(transaction), emptySet(), progressTracker)
|
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
object NOTARISING : ProgressTracker.Step("Requesting signature by notary service") {
|
object NOTARISING : ProgressTracker.Step("Requesting signature by notary service") {
|
||||||
@ -49,52 +38,41 @@ open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
|
|||||||
|
|
||||||
object BROADCASTING : ProgressTracker.Step("Broadcasting transaction to participants")
|
object BROADCASTING : ProgressTracker.Step("Broadcasting transaction to participants")
|
||||||
|
|
||||||
// TODO: Make all tracker() methods @JvmStatic
|
@JvmStatic
|
||||||
fun tracker() = ProgressTracker(NOTARISING, BROADCASTING)
|
fun tracker() = ProgressTracker(NOTARISING, BROADCASTING)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Throws(NotaryException::class)
|
@Throws(NotaryException::class)
|
||||||
override fun call(): List<SignedTransaction> {
|
override fun call(): SignedTransaction {
|
||||||
// Note: this method is carefully broken up to minimize the amount of data reachable from the stack at
|
// Note: this method is carefully broken up to minimize the amount of data reachable from the stack at
|
||||||
// the point where subFlow is invoked, as that minimizes the checkpointing work to be done.
|
// the point where subFlow is invoked, as that minimizes the checkpointing work to be done.
|
||||||
//
|
//
|
||||||
// Lookup the resolved transactions and use them to map each signed transaction to the list of participants.
|
// Lookup the resolved transactions and use them to map each signed transaction to the list of participants.
|
||||||
// Then send to the notary if needed, record locally and distribute.
|
// Then send to the notary if needed, record locally and distribute.
|
||||||
|
val parties = getPartiesToSend(verifyTx())
|
||||||
progressTracker.currentStep = NOTARISING
|
progressTracker.currentStep = NOTARISING
|
||||||
val notarisedTxns: List<Pair<SignedTransaction, Set<Party>>> = resolveDependenciesOf(transactions)
|
val notarised = notariseAndRecord()
|
||||||
.map { (stx, ltx) -> Pair(notariseAndRecord(stx), lookupParties(ltx)) }
|
|
||||||
|
|
||||||
// Each transaction has its own set of recipients, but extra recipients get them all.
|
// Each transaction has its own set of recipients, but extra recipients get them all.
|
||||||
progressTracker.currentStep = BROADCASTING
|
progressTracker.currentStep = BROADCASTING
|
||||||
for ((stx, parties) in notarisedTxns) {
|
for (party in parties) {
|
||||||
val participants = (parties + extraRecipients).filter { !serviceHub.myInfo.isLegalIdentity(it) }.toSet()
|
if (!serviceHub.myInfo.isLegalIdentity(party)) {
|
||||||
if (participants.isNotEmpty()) {
|
val session = initiateFlow(party)
|
||||||
broadcastTransaction(stx, participants.toNonEmptySet())
|
subFlow(SendTransactionFlow(session, notarised))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return notarisedTxns.map { it.first }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return notarised
|
||||||
* Broadcast a transaction to the participants. By default calls [BroadcastTransactionFlow], however can be
|
|
||||||
* overridden for more complex transaction delivery protocols (for example where not all parties know each other).
|
|
||||||
*
|
|
||||||
* @param participants the participants to send the transaction to. This is expected to include extra participants
|
|
||||||
* and exclude the local node.
|
|
||||||
*/
|
|
||||||
@Suspendable
|
|
||||||
open protected fun broadcastTransaction(stx: SignedTransaction, participants: NonEmptySet<Party>) {
|
|
||||||
subFlow(BroadcastTransactionFlow(stx, participants))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun notariseAndRecord(stx: SignedTransaction): SignedTransaction {
|
private fun notariseAndRecord(): SignedTransaction {
|
||||||
val notarised = if (needsNotarySignature(stx)) {
|
val notarised = if (needsNotarySignature(transaction)) {
|
||||||
val notarySignatures = subFlow(NotaryFlow.Client(stx))
|
val notarySignatures = subFlow(NotaryFlow.Client(transaction))
|
||||||
stx + notarySignatures
|
transaction + notarySignatures
|
||||||
} else {
|
} else {
|
||||||
stx
|
transaction
|
||||||
}
|
}
|
||||||
serviceHub.recordTransactions(notarised)
|
serviceHub.recordTransactions(notarised)
|
||||||
return notarised
|
return notarised
|
||||||
@ -112,47 +90,17 @@ open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
|
|||||||
return !(notaryKey?.isFulfilledBy(signers) ?: false)
|
return !(notaryKey?.isFulfilledBy(signers) ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun getPartiesToSend(ltx: LedgerTransaction): Set<Party> {
|
||||||
* Resolve the parties involved in a transaction.
|
val participants = ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants }
|
||||||
*
|
return serviceHub.groupAbstractPartyByWellKnownParty(participants).keys + extraRecipients
|
||||||
* The default implementation throws an exception if an unknown party is encountered.
|
|
||||||
*/
|
|
||||||
open protected fun lookupParties(ltx: LedgerTransaction): Set<Party> {
|
|
||||||
// Calculate who is meant to see the results based on the participants involved.
|
|
||||||
return extractParticipants(ltx).map {
|
|
||||||
serviceHub.identityService.partyFromAnonymous(it)
|
|
||||||
?: throw IllegalArgumentException("Could not resolve well known identity of participant $it")
|
|
||||||
}.toSet()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun verifyTx(): LedgerTransaction {
|
||||||
* Helper function to extract all participants from a ledger transaction. Intended to help implement [lookupParties]
|
val notary = transaction.tx.notary
|
||||||
* overriding functions.
|
// The notary signature(s) are allowed to be missing but no others.
|
||||||
*/
|
if (notary != null) transaction.verifySignaturesExcept(notary.owningKey) else transaction.verifyRequiredSignatures()
|
||||||
protected fun extractParticipants(ltx: LedgerTransaction): List<AbstractParty> {
|
val ltx = transaction.toLedgerTransaction(serviceHub, false)
|
||||||
return ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants }
|
ltx.verify()
|
||||||
}
|
return ltx
|
||||||
|
|
||||||
private fun resolveDependenciesOf(signedTransactions: Iterable<SignedTransaction>): List<Pair<SignedTransaction, LedgerTransaction>> {
|
|
||||||
// Make sure the dependencies come before the dependers.
|
|
||||||
val sorted = ResolveTransactionsFlow.topologicalSort(signedTransactions.toList())
|
|
||||||
// Build a ServiceHub that consults the argument list as well as what's in local tx storage so uncommitted
|
|
||||||
// transactions can depend on each other.
|
|
||||||
val augmentedLookup = object : ServiceHub by serviceHub {
|
|
||||||
val hashToTx = sorted.associateBy { it.id }
|
|
||||||
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
|
||||||
val provided: TransactionState<ContractState>? = hashToTx[stateRef.txhash]?.let { it.tx.outputs[stateRef.index] }
|
|
||||||
return provided ?: super.loadState(stateRef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Load and verify each transaction.
|
|
||||||
return sorted.map { stx ->
|
|
||||||
val notary = stx.tx.notary
|
|
||||||
// The notary signature(s) are allowed to be missing but no others.
|
|
||||||
if (notary != null) stx.verifySignaturesExcept(notary.owningKey) else stx.verifyRequiredSignatures()
|
|
||||||
val ltx = stx.toLedgerTransaction(augmentedLookup, false)
|
|
||||||
ltx.verify()
|
|
||||||
stx to ltx
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import net.corda.core.CordaRuntimeException
|
|||||||
/**
|
/**
|
||||||
* Exception which can be thrown by a [FlowLogic] at any point in its logic to unexpectedly bring it to a permanent end.
|
* Exception which can be thrown by a [FlowLogic] at any point in its logic to unexpectedly bring it to a permanent end.
|
||||||
* The exception will propagate to all counterparty flows and will be thrown on their end the next time they wait on a
|
* The exception will propagate to all counterparty flows and will be thrown on their end the next time they wait on a
|
||||||
* [FlowLogic.receive] or [FlowLogic.sendAndReceive]. Any flow which no longer needs to do a receive, or has already ended,
|
* [FlowSession.receive] or [FlowSession.sendAndReceive]. Any flow which no longer needs to do a receive, or has already ended,
|
||||||
* will not receive the exception (if this is required then have them wait for a confirmation message).
|
* will not receive the exception (if this is required then have them wait for a confirmation message).
|
||||||
*
|
*
|
||||||
* [FlowException] (or a subclass) can be a valid expected response from a flow, particularly ones which act as a service.
|
* [FlowException] (or a subclass) can be a valid expected response from a flow, particularly ones which act as a service.
|
||||||
|
@ -137,11 +137,17 @@ abstract class FlowLogic<out T> {
|
|||||||
internal inline fun <reified R : Any> sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData<R> {
|
internal inline fun <reified R : Any> sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData<R> {
|
||||||
return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true)
|
return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
internal fun <R : Any> FlowSession.sendAndReceiveWithRetry(receiveType: Class<R>, payload: Any): UntrustworthyData<R> {
|
internal fun <R : Any> FlowSession.sendAndReceiveWithRetry(receiveType: Class<R>, payload: Any): UntrustworthyData<R> {
|
||||||
return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true)
|
return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
internal inline fun <reified R : Any> FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData<R> {
|
||||||
|
return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suspends until the specified [otherParty] sends us a message of type [R].
|
* Suspends until the specified [otherParty] sends us a message of type [R].
|
||||||
*
|
*
|
||||||
|
@ -23,12 +23,12 @@ import net.corda.core.utilities.UntrustworthyData
|
|||||||
*
|
*
|
||||||
* If it's an InitiatedBy flow:
|
* If it's an InitiatedBy flow:
|
||||||
*
|
*
|
||||||
* Change the constructor to take an initiatingSession: FlowSession instead of a counterparty: Party
|
* Change the constructor to take an otherSideSession: FlowSession instead of a counterparty: Party
|
||||||
* Then look for usages of the deprecated functions and change them to use the FlowSession
|
* Then look for usages of the deprecated functions and change them to use the FlowSession
|
||||||
* For example:
|
* For example:
|
||||||
* send(counterparty, something)
|
* send(counterparty, something)
|
||||||
* will become
|
* will become
|
||||||
* initiatingSession.send(something)
|
* otherSideSession.send(something)
|
||||||
*/
|
*/
|
||||||
abstract class FlowSession {
|
abstract class FlowSession {
|
||||||
abstract val counterparty: Party
|
abstract val counterparty: Party
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package net.corda.core.flows
|
|
||||||
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.core.utilities.ProgressTracker
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alternative finality flow which only does not attempt to take participants from the transaction, but instead all
|
|
||||||
* participating parties must be provided manually.
|
|
||||||
*
|
|
||||||
* @param transactions What to commit.
|
|
||||||
* @param recipients List of participants to inform of the transaction.
|
|
||||||
*/
|
|
||||||
class ManualFinalityFlow(transactions: Iterable<SignedTransaction>,
|
|
||||||
recipients: Set<Party>,
|
|
||||||
progressTracker: ProgressTracker) : FinalityFlow(transactions, recipients, progressTracker) {
|
|
||||||
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(listOf(transaction), extraParticipants, tracker())
|
|
||||||
override fun lookupParties(ltx: LedgerTransaction): Set<Party> = emptySet()
|
|
||||||
}
|
|
@ -43,7 +43,7 @@ class NotaryChangeFlow<out T : ContractState>(
|
|||||||
val mySignature = serviceHub.keyManagementService.sign(signableData, myKey)
|
val mySignature = serviceHub.keyManagementService.sign(signableData, myKey)
|
||||||
val stx = SignedTransaction(tx, listOf(mySignature))
|
val stx = SignedTransaction(tx, listOf(mySignature))
|
||||||
|
|
||||||
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
return AbstractStateReplacementFlow.UpgradeTx(stx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolves the encumbrance state chain for the given [state] */
|
/** Resolves the encumbrance state chain for the given [state] */
|
||||||
|
@ -65,16 +65,17 @@ class NotaryFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val response = try {
|
val response = try {
|
||||||
|
val session = initiateFlow(notaryParty)
|
||||||
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
|
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
|
||||||
subFlow(SendTransactionWithRetry(notaryParty, stx))
|
subFlow(SendTransactionWithRetry(session, stx))
|
||||||
receive<List<TransactionSignature>>(notaryParty)
|
session.receive<List<TransactionSignature>>()
|
||||||
} else {
|
} else {
|
||||||
val tx: Any = if (stx.isNotaryChangeTransaction()) {
|
val tx: Any = if (stx.isNotaryChangeTransaction()) {
|
||||||
stx.notaryChangeTx
|
stx.notaryChangeTx
|
||||||
} else {
|
} else {
|
||||||
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
||||||
}
|
}
|
||||||
sendAndReceiveWithRetry(notaryParty, tx)
|
session.sendAndReceiveWithRetry(tx)
|
||||||
}
|
}
|
||||||
} catch (e: NotaryException) {
|
} catch (e: NotaryException) {
|
||||||
if (e.error is NotaryError.Conflict) {
|
if (e.error is NotaryError.Conflict) {
|
||||||
@ -99,10 +100,10 @@ class NotaryFlow {
|
|||||||
* The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry]
|
* The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry]
|
||||||
* instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only.
|
* instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only.
|
||||||
*/
|
*/
|
||||||
private class SendTransactionWithRetry(otherSide: Party, stx: SignedTransaction) : SendTransactionFlow(otherSide, stx) {
|
private class SendTransactionWithRetry(otherSideSession: FlowSession, stx: SignedTransaction) : SendTransactionFlow(otherSideSession, stx) {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
||||||
return sendAndReceiveWithRetry(otherSide, payload)
|
return otherSideSession.sendAndReceiveWithRetry(payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,14 +116,14 @@ class NotaryFlow {
|
|||||||
* Additional transaction validation logic can be added when implementing [receiveAndVerifyTx].
|
* Additional transaction validation logic can be added when implementing [receiveAndVerifyTx].
|
||||||
*/
|
*/
|
||||||
// See AbstractStateReplacementFlow.Acceptor for why it's Void?
|
// See AbstractStateReplacementFlow.Acceptor for why it's Void?
|
||||||
abstract class Service(val otherSide: Party, val service: TrustedAuthorityNotaryService) : FlowLogic<Void?>() {
|
abstract class Service(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic<Void?>() {
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Void? {
|
override fun call(): Void? {
|
||||||
val (id, inputs, timeWindow, notary) = receiveAndVerifyTx()
|
val (id, inputs, timeWindow, notary) = receiveAndVerifyTx()
|
||||||
checkNotary(notary)
|
checkNotary(notary)
|
||||||
service.validateTimeWindow(timeWindow)
|
service.validateTimeWindow(timeWindow)
|
||||||
service.commitInputStates(inputs, id, otherSide)
|
service.commitInputStates(inputs, id, otherSideSession.counterparty)
|
||||||
signAndSendResponse(id)
|
signAndSendResponse(id)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -144,7 +145,7 @@ class NotaryFlow {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
private fun signAndSendResponse(txId: SecureHash) {
|
private fun signAndSendResponse(txId: SecureHash) {
|
||||||
val signature = service.sign(txId)
|
val signature = service.sign(txId)
|
||||||
send(otherSide, listOf(signature))
|
otherSideSession.send(listOf(signature))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.core.flows
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.internal.ResolveTransactionsFlow
|
import net.corda.core.internal.ResolveTransactionsFlow
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -11,18 +10,26 @@ import java.security.SignatureException
|
|||||||
/**
|
/**
|
||||||
* The [ReceiveTransactionFlow] should be called in response to the [SendTransactionFlow].
|
* The [ReceiveTransactionFlow] should be called in response to the [SendTransactionFlow].
|
||||||
*
|
*
|
||||||
* This flow is a combination of [receive], resolve and [SignedTransaction.verify]. This flow will receive the [SignedTransaction]
|
* This flow is a combination of [FlowSession.receive], resolve and [SignedTransaction.verify]. This flow will receive the
|
||||||
* and perform the resolution back-and-forth required to check the dependencies and download any missing attachments.
|
* [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing
|
||||||
* The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify].
|
* attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify].
|
||||||
|
*
|
||||||
|
* @param otherSideSession session to the other side which is calling [SendTransactionFlow].
|
||||||
|
* @param checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify].
|
||||||
*/
|
*/
|
||||||
class ReceiveTransactionFlow
|
class ReceiveTransactionFlow(private val otherSideSession: FlowSession,
|
||||||
@JvmOverloads
|
private val checkSufficientSignatures: Boolean) : FlowLogic<SignedTransaction>() {
|
||||||
constructor(private val otherParty: Party, private val checkSufficientSignatures: Boolean = true) : FlowLogic<SignedTransaction>() {
|
/** Receives a [SignedTransaction] from [otherSideSession], verifies it and then records it in the vault. */
|
||||||
|
constructor(otherSideSession: FlowSession) : this(otherSideSession, true)
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
|
@Throws(SignatureException::class,
|
||||||
|
AttachmentResolutionException::class,
|
||||||
|
TransactionResolutionException::class,
|
||||||
|
TransactionVerificationException::class)
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
return receive<SignedTransaction>(otherParty).unwrap {
|
return otherSideSession.receive<SignedTransaction>().unwrap {
|
||||||
subFlow(ResolveTransactionsFlow(it, otherParty))
|
subFlow(ResolveTransactionsFlow(it, otherSideSession))
|
||||||
it.verify(serviceHub, checkSufficientSignatures)
|
it.verify(serviceHub, checkSufficientSignatures)
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
@ -32,16 +39,16 @@ constructor(private val otherParty: Party, private val checkSufficientSignatures
|
|||||||
/**
|
/**
|
||||||
* The [ReceiveStateAndRefFlow] should be called in response to the [SendStateAndRefFlow].
|
* The [ReceiveStateAndRefFlow] should be called in response to the [SendStateAndRefFlow].
|
||||||
*
|
*
|
||||||
* This flow is a combination of [receive] and resolve. This flow will receive a list of [StateAndRef]
|
* This flow is a combination of [FlowSession.receive] and resolve. This flow will receive a list of [StateAndRef]
|
||||||
* and perform the resolution back-and-forth required to check the dependencies.
|
* and perform the resolution back-and-forth required to check the dependencies.
|
||||||
* The flow will return the list of [StateAndRef] after it is resolved.
|
* The flow will return the list of [StateAndRef] after it is resolved.
|
||||||
*/
|
*/
|
||||||
// @JvmSuppressWildcards is used to suppress wildcards in return type when calling `subFlow(new ReceiveStateAndRef<T>(otherParty))` in java.
|
// @JvmSuppressWildcards is used to suppress wildcards in return type when calling `subFlow(new ReceiveStateAndRef<T>(otherParty))` in java.
|
||||||
class ReceiveStateAndRefFlow<out T : ContractState>(private val otherParty: Party) : FlowLogic<@JvmSuppressWildcards List<StateAndRef<T>>>() {
|
class ReceiveStateAndRefFlow<out T : ContractState>(private val otherSideSession: FlowSession) : FlowLogic<@JvmSuppressWildcards List<StateAndRef<T>>>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): List<StateAndRef<T>> {
|
override fun call(): List<StateAndRef<T>> {
|
||||||
return receive<List<StateAndRef<T>>>(otherParty).unwrap {
|
return otherSideSession.receive<List<StateAndRef<T>>>().unwrap {
|
||||||
subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherParty))
|
subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherSideSession))
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.core.flows
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.internal.FetchDataFlow
|
import net.corda.core.internal.FetchDataFlow
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -14,9 +13,9 @@ import net.corda.core.utilities.unwrap
|
|||||||
* to check the dependencies and download any missing attachments.
|
* to check the dependencies and download any missing attachments.
|
||||||
*
|
*
|
||||||
* @param otherSide the target party.
|
* @param otherSide the target party.
|
||||||
* @param stx the [SignedTransaction] being sent to the [otherSide].
|
* @param stx the [SignedTransaction] being sent to the [otherSideSession].
|
||||||
*/
|
*/
|
||||||
open class SendTransactionFlow(otherSide: Party, stx: SignedTransaction) : DataVendingFlow(otherSide, stx)
|
open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) : DataVendingFlow(otherSide, stx)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [SendStateAndRefFlow] should be used to send a list of input [StateAndRef] to another peer that wishes to verify
|
* The [SendStateAndRefFlow] should be used to send a list of input [StateAndRef] to another peer that wishes to verify
|
||||||
@ -24,14 +23,14 @@ open class SendTransactionFlow(otherSide: Party, stx: SignedTransaction) : DataV
|
|||||||
* at the right point in the conversation to receive the input state and ref and perform the resolution back-and-forth
|
* at the right point in the conversation to receive the input state and ref and perform the resolution back-and-forth
|
||||||
* required to check the dependencies.
|
* required to check the dependencies.
|
||||||
*
|
*
|
||||||
* @param otherSide the target party.
|
* @param otherSideSession the target session.
|
||||||
* @param stateAndRefs the list of [StateAndRef] being sent to the [otherSide].
|
* @param stateAndRefs the list of [StateAndRef] being sent to the [otherSideSession].
|
||||||
*/
|
*/
|
||||||
open class SendStateAndRefFlow(otherSide: Party, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSide, stateAndRefs)
|
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
|
||||||
|
|
||||||
sealed class DataVendingFlow(val otherSide: Party, val payload: Any) : FlowLogic<Void?>() {
|
sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected open fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any) = sendAndReceive<FetchDataFlow.Request>(otherSide, payload)
|
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected open fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) {
|
protected open fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) {
|
||||||
@ -42,11 +41,11 @@ sealed class DataVendingFlow(val otherSide: Party, val payload: Any) : FlowLogic
|
|||||||
override fun call(): Void? {
|
override fun call(): Void? {
|
||||||
// The first payload will be the transaction data, subsequent payload will be the transaction/attachment data.
|
// The first payload will be the transaction data, subsequent payload will be the transaction/attachment data.
|
||||||
var payload = payload
|
var payload = payload
|
||||||
// This loop will receive [FetchDataFlow.Request] continuously until the `otherSide` has all the data they need
|
// This loop will receive [FetchDataFlow.Request] continuously until the `otherSideSession` has all the data they need
|
||||||
// to resolve the transaction, a [FetchDataFlow.EndRequest] will be sent from the `otherSide` to indicate end of
|
// to resolve the transaction, a [FetchDataFlow.EndRequest] will be sent from the `otherSideSession` to indicate end of
|
||||||
// data request.
|
// data request.
|
||||||
while (true) {
|
while (true) {
|
||||||
val dataRequest = sendPayloadAndReceiveDataRequest(otherSide, payload).unwrap { request ->
|
val dataRequest = sendPayloadAndReceiveDataRequest(otherSideSession, payload).unwrap { request ->
|
||||||
when (request) {
|
when (request) {
|
||||||
is FetchDataFlow.Request.Data -> {
|
is FetchDataFlow.Request.Data -> {
|
||||||
// Security TODO: Check for abnormally large or malformed data requests
|
// Security TODO: Check for abnormally large or malformed data requests
|
||||||
|
@ -7,7 +7,7 @@ import net.corda.core.crypto.SecureHash
|
|||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.internal.FetchDataFlow.DownloadedVsRequestedDataMismatch
|
import net.corda.core.internal.FetchDataFlow.DownloadedVsRequestedDataMismatch
|
||||||
import net.corda.core.internal.FetchDataFlow.HashNotFound
|
import net.corda.core.internal.FetchDataFlow.HashNotFound
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -38,7 +38,7 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
||||||
protected val requests: Set<SecureHash>,
|
protected val requests: Set<SecureHash>,
|
||||||
protected val otherSide: Party,
|
protected val otherSideSession: FlowSession,
|
||||||
protected val dataType: DataType) : FlowLogic<FetchDataFlow.Result<T>>() {
|
protected val dataType: DataType) : FlowLogic<FetchDataFlow.Result<T>>() {
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
@ -72,7 +72,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
|||||||
return if (toFetch.isEmpty()) {
|
return if (toFetch.isEmpty()) {
|
||||||
Result(fromDisk, emptyList())
|
Result(fromDisk, emptyList())
|
||||||
} else {
|
} else {
|
||||||
logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSide.name}")
|
logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSideSession.counterparty.name}")
|
||||||
|
|
||||||
// TODO: Support "large message" response streaming so response sizes are not limited by RAM.
|
// TODO: Support "large message" response streaming so response sizes are not limited by RAM.
|
||||||
// We can then switch to requesting items in large batches to minimise the latency penalty.
|
// We can then switch to requesting items in large batches to minimise the latency penalty.
|
||||||
@ -85,11 +85,11 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
|||||||
for (hash in toFetch) {
|
for (hash in toFetch) {
|
||||||
// We skip the validation here (with unwrap { it }) because we will do it below in validateFetchResponse.
|
// We skip the validation here (with unwrap { it }) because we will do it below in validateFetchResponse.
|
||||||
// The only thing checked is the object type. It is a protocol violation to send results out of order.
|
// The only thing checked is the object type. It is a protocol violation to send results out of order.
|
||||||
maybeItems += sendAndReceive<List<W>>(otherSide, Request.Data(NonEmptySet.of(hash), dataType)).unwrap { it }
|
maybeItems += otherSideSession.sendAndReceive<List<W>>(Request.Data(NonEmptySet.of(hash), dataType)).unwrap { it }
|
||||||
}
|
}
|
||||||
// Check for a buggy/malicious peer answering with something that we didn't ask for.
|
// Check for a buggy/malicious peer answering with something that we didn't ask for.
|
||||||
val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch)
|
val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch)
|
||||||
logger.info("Fetched ${downloaded.size} elements from ${otherSide.name}")
|
logger.info("Fetched ${downloaded.size} elements from ${otherSideSession.counterparty.name}")
|
||||||
maybeWriteToDisk(downloaded)
|
maybeWriteToDisk(downloaded)
|
||||||
Result(fromDisk, downloaded)
|
Result(fromDisk, downloaded)
|
||||||
}
|
}
|
||||||
@ -140,7 +140,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
|||||||
* attachments are saved to local storage automatically.
|
* attachments are saved to local storage automatically.
|
||||||
*/
|
*/
|
||||||
class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
||||||
otherSide: Party) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide, DataType.ATTACHMENT) {
|
otherSide: FlowSession) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide, DataType.ATTACHMENT) {
|
||||||
|
|
||||||
override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid)
|
override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid)
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
|||||||
* results in a [FetchDataFlow.HashNotFound] exception. Note that returned transactions are not inserted into
|
* results in a [FetchDataFlow.HashNotFound] exception. Note that returned transactions are not inserted into
|
||||||
* the database, because it's up to the caller to actually verify the transactions are valid.
|
* the database, because it's up to the caller to actually verify the transactions are valid.
|
||||||
*/
|
*/
|
||||||
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: Party) :
|
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
|
||||||
FetchDataFlow<SignedTransaction, SignedTransaction>(requests, otherSide, DataType.TRANSACTION) {
|
FetchDataFlow<SignedTransaction, SignedTransaction>(requests, otherSide, DataType.TRANSACTION) {
|
||||||
|
|
||||||
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid)
|
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid)
|
||||||
|
@ -272,3 +272,5 @@ annotation class VisibleForTesting
|
|||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T, U : T> uncheckedCast(obj: T) = obj as U
|
fun <T, U : T> uncheckedCast(obj: T) = obj as U
|
||||||
|
|
||||||
|
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }
|
@ -3,7 +3,7 @@ package net.corda.core.internal
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.exactAdd
|
import net.corda.core.utilities.exactAdd
|
||||||
@ -17,14 +17,14 @@ import java.util.*
|
|||||||
* @return a list of verified [SignedTransaction] objects, in a depth-first order.
|
* @return a list of verified [SignedTransaction] objects, in a depth-first order.
|
||||||
*/
|
*/
|
||||||
class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
|
class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
|
||||||
private val otherSide: Party) : FlowLogic<List<SignedTransaction>>() {
|
private val otherSide: FlowSession) : FlowLogic<List<SignedTransaction>>() {
|
||||||
/**
|
/**
|
||||||
* Resolves and validates the dependencies of the specified [signedTransaction]. Fetches the attachments, but does
|
* Resolves and validates the dependencies of the specified [signedTransaction]. Fetches the attachments, but does
|
||||||
* *not* validate or store the [signedTransaction] itself.
|
* *not* validate or store the [signedTransaction] itself.
|
||||||
*
|
*
|
||||||
* @return a list of verified [SignedTransaction] objects, in a depth-first order.
|
* @return a list of verified [SignedTransaction] objects, in a depth-first order.
|
||||||
*/
|
*/
|
||||||
constructor(signedTransaction: SignedTransaction, otherSide: Party) : this(dependencyIDs(signedTransaction), otherSide) {
|
constructor(signedTransaction: SignedTransaction, otherSide: FlowSession) : this(dependencyIDs(signedTransaction), otherSide) {
|
||||||
this.signedTransaction = signedTransaction
|
this.signedTransaction = signedTransaction
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
@ -82,7 +82,7 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
|
|||||||
// Start fetching data.
|
// Start fetching data.
|
||||||
val newTxns = downloadDependencies(txHashes)
|
val newTxns = downloadDependencies(txHashes)
|
||||||
fetchMissingAttachments(signedTransaction?.let { newTxns + it } ?: newTxns)
|
fetchMissingAttachments(signedTransaction?.let { newTxns + it } ?: newTxns)
|
||||||
send(otherSide, FetchDataFlow.Request.End)
|
otherSide.send(FetchDataFlow.Request.End)
|
||||||
// Finish fetching data.
|
// Finish fetching data.
|
||||||
|
|
||||||
val result = topologicalSort(newTxns)
|
val result = topologicalSort(newTxns)
|
||||||
|
@ -5,8 +5,10 @@ import net.corda.core.crypto.Crypto
|
|||||||
import net.corda.core.crypto.SignableData
|
import net.corda.core.crypto.SignableData
|
||||||
import net.corda.core.crypto.SignatureMetadata
|
import net.corda.core.crypto.SignatureMetadata
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.internal.toMultiMap
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
@ -269,4 +271,75 @@ interface ServiceHub : ServicesForResolution {
|
|||||||
* @return A new [Connection]
|
* @return A new [Connection]
|
||||||
*/
|
*/
|
||||||
fun jdbcSession(): Connection
|
fun jdbcSession(): Connection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group each [PublicKey] by the well known party using the [ServiceHub.identityService], in preparation for
|
||||||
|
* creating [FlowSession]s, for example.
|
||||||
|
*
|
||||||
|
* @param publicKeys the [PublicKey]s to group.
|
||||||
|
* @param ignoreUnrecognisedParties if this is false, throw an exception if some of the [PublicKey]s cannot be mapped
|
||||||
|
* to a [Party].
|
||||||
|
* @return a map of well known [Party] to associated [PublicKey]s.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
fun groupPublicKeysByWellKnownParty(publicKeys: Collection<PublicKey>, ignoreUnrecognisedParties: Boolean): Map<Party, List<PublicKey>> =
|
||||||
|
groupAbstractPartyByWellKnownParty(publicKeys.map { AnonymousParty(it) }, ignoreUnrecognisedParties).mapValues { it.value.map { it.owningKey } }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group each [PublicKey] by the well known party using the [ServiceHub.identityService], in preparation for
|
||||||
|
* creating [FlowSession]s, for example. Throw an exception if some of the [PublicKey]s cannot be mapped
|
||||||
|
* to a [Party].
|
||||||
|
*
|
||||||
|
* @param publicKeys the [PublicKey]s to group.
|
||||||
|
* @return a map of well known [Party] to associated [PublicKey]s.
|
||||||
|
*/
|
||||||
|
// Cannot use @JvmOverloads in interface
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
fun groupPublicKeysByWellKnownParty(publicKeys: Collection<PublicKey>): Map<Party, List<PublicKey>> = groupPublicKeysByWellKnownParty(publicKeys, false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group each [AbstractParty] by the well known party using the [ServiceHub.identityService], in preparation for
|
||||||
|
* creating [FlowSession]s, for example.
|
||||||
|
*
|
||||||
|
* @param parties the [AbstractParty]s to group.
|
||||||
|
* @param ignoreUnrecognisedParties if this is false, throw an exception if some of the [AbstractParty]s cannot be mapped
|
||||||
|
* to a [Party].
|
||||||
|
* @return a map of well known [Party] to associated [AbstractParty]s.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
fun groupAbstractPartyByWellKnownParty(parties: Collection<AbstractParty>, ignoreUnrecognisedParties: Boolean): Map<Party, List<AbstractParty>> {
|
||||||
|
val partyToPublicKey: Iterable<Pair<Party, AbstractParty>> = parties.mapNotNull {
|
||||||
|
(identityService.partyFromAnonymous(it) ?: if (ignoreUnrecognisedParties) return@mapNotNull null else throw IllegalArgumentException("Could not find Party for $it")) to it
|
||||||
|
}
|
||||||
|
return partyToPublicKey.toMultiMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group each [AbstractParty] by the well known party using the [ServiceHub.identityService], in preparation for
|
||||||
|
* creating [FlowSession]s, for example. Throw an exception if some of the [AbstractParty]s cannot be mapped
|
||||||
|
* to a [Party].
|
||||||
|
*
|
||||||
|
* @param parties the [AbstractParty]s to group.
|
||||||
|
* @return a map of well known [Party] to associated [AbstractParty]s.
|
||||||
|
*/
|
||||||
|
// Cannot use @JvmOverloads in interface
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
fun groupAbstractPartyByWellKnownParty(parties: Collection<AbstractParty>): Map<Party, List<AbstractParty>> {
|
||||||
|
return groupAbstractPartyByWellKnownParty(parties, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove this node from a map of well known [Party]s.
|
||||||
|
*
|
||||||
|
* @return a new copy of the map, with the well known [Party] for this node removed.
|
||||||
|
*/
|
||||||
|
fun <T> excludeMe(map: Map<Party, T>): Map<Party, T> = map.filterKeys { !myInfo.isLegalIdentity(it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the [Party] associated with the notary of a [SignedTransaction] from the a map of [Party]s. It is a no-op
|
||||||
|
* if the notary is null.
|
||||||
|
*
|
||||||
|
* @return a new copy of the map, with the well known [Party] for the notary removed.
|
||||||
|
*/
|
||||||
|
fun <T> excludeNotary(map: Map<Party, T>, stx: SignedTransaction): Map<Party, T> = map.filterKeys { it != stx.notary }
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@ package net.corda.core.node.services
|
|||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.NotaryError
|
|
||||||
import net.corda.core.flows.NotaryException
|
|
||||||
import net.corda.core.flows.NotaryFlow
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
@ -24,9 +21,9 @@ abstract class NotaryService : SingletonSerializeAsToken() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Produces a notary service flow which has the corresponding sends and receives as [NotaryFlow.Client].
|
* Produces a notary service flow which has the corresponding sends and receives as [NotaryFlow.Client].
|
||||||
* @param otherParty client [Party] making the request
|
* @param otherPartySession client [Party] making the request
|
||||||
*/
|
*/
|
||||||
abstract fun createServiceFlow(otherParty: Party): FlowLogic<Void?>
|
abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,7 +42,7 @@ interface TransactionWithSignatures : NamedByHash {
|
|||||||
fun verifySignaturesExcept(vararg allowedToBeMissing: PublicKey) {
|
fun verifySignaturesExcept(vararg allowedToBeMissing: PublicKey) {
|
||||||
checkSignaturesAreValid()
|
checkSignaturesAreValid()
|
||||||
|
|
||||||
val needed = getMissingSignatures() - allowedToBeMissing
|
val needed = getMissingSigners() - allowedToBeMissing
|
||||||
if (needed.isNotEmpty())
|
if (needed.isNotEmpty())
|
||||||
throw SignaturesMissingException(needed.toNonEmptySet(), getKeyDescriptions(needed), id)
|
throw SignaturesMissingException(needed.toNonEmptySet(), getKeyDescriptions(needed), id)
|
||||||
}
|
}
|
||||||
@ -71,7 +71,10 @@ interface TransactionWithSignatures : NamedByHash {
|
|||||||
*/
|
*/
|
||||||
fun getKeyDescriptions(keys: Set<PublicKey>): List<String>
|
fun getKeyDescriptions(keys: Set<PublicKey>): List<String>
|
||||||
|
|
||||||
private fun getMissingSignatures(): Set<PublicKey> {
|
/**
|
||||||
|
* Return the [PublicKey]s for which we still need signatures.
|
||||||
|
*/
|
||||||
|
fun getMissingSigners(): Set<PublicKey> {
|
||||||
val sigKeys = sigs.map { it.by }.toSet()
|
val sigKeys = sigs.map { it.by }.toSet()
|
||||||
// TODO Problem is that we can get single PublicKey wrapped as CompositeKey in allowedToBeMissing/mustSign
|
// TODO Problem is that we can get single PublicKey wrapped as CompositeKey in allowedToBeMissing/mustSign
|
||||||
// equals on CompositeKey won't catch this case (do we want to single PublicKey be equal to the same key wrapped in CompositeKey with threshold 1?)
|
// equals on CompositeKey won't catch this case (do we want to single PublicKey be equal to the same key wrapped in CompositeKey with threshold 1?)
|
||||||
|
@ -80,8 +80,9 @@ public class FlowsInJavaTest {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public String call() throws FlowException {
|
public String call() throws FlowException {
|
||||||
return receive(String.class, otherParty).unwrap(data -> {
|
FlowSession session = initiateFlow(otherParty);
|
||||||
send(otherParty, "Something");
|
return session.receive(String.class).unwrap(data -> {
|
||||||
|
session.send("Something");
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -89,16 +90,16 @@ public class FlowsInJavaTest {
|
|||||||
|
|
||||||
@InitiatedBy(SendInUnwrapFlow.class)
|
@InitiatedBy(SendInUnwrapFlow.class)
|
||||||
private static class SendHelloAndThenReceive extends FlowLogic<String> {
|
private static class SendHelloAndThenReceive extends FlowLogic<String> {
|
||||||
private final Party otherParty;
|
private final FlowSession otherSide;
|
||||||
|
|
||||||
private SendHelloAndThenReceive(Party otherParty) {
|
private SendHelloAndThenReceive(FlowSession otherParty) {
|
||||||
this.otherParty = otherParty;
|
this.otherSide = otherParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public String call() throws FlowException {
|
public String call() throws FlowException {
|
||||||
return sendAndReceive(String.class, otherParty, "Hello").unwrap(data -> data);
|
return otherSide.sendAndReceive(String.class, "Hello").unwrap(data -> data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +116,8 @@ public class FlowsInJavaTest {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public Void call() throws FlowException {
|
public Void call() throws FlowException {
|
||||||
receive(Primitives.unwrap(receiveType), otherParty);
|
FlowSession session = initiateFlow(otherParty);
|
||||||
|
session.receive(Primitives.unwrap(receiveType));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,12 +162,15 @@ class AttachmentTests {
|
|||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
private class InitiatingFetchAttachmentsFlow(val otherSide: Party, val hashes: Set<SecureHash>) : FlowLogic<FetchDataFlow.Result<Attachment>>() {
|
private class InitiatingFetchAttachmentsFlow(val otherSide: Party, val hashes: Set<SecureHash>) : FlowLogic<FetchDataFlow.Result<Attachment>>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): FetchDataFlow.Result<Attachment> = subFlow(FetchAttachmentsFlow(hashes, otherSide))
|
override fun call(): FetchDataFlow.Result<Attachment> {
|
||||||
|
val session = initiateFlow(otherSide)
|
||||||
|
return subFlow(FetchAttachmentsFlow(hashes, session))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(InitiatingFetchAttachmentsFlow::class)
|
@InitiatedBy(InitiatingFetchAttachmentsFlow::class)
|
||||||
private class FetchAttachmentsResponse(val otherSide: Party) : FlowLogic<Void?>() {
|
private class FetchAttachmentsResponse(val otherSideSession: FlowSession) : FlowLogic<Void?>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = subFlow(TestDataVendingFlow(otherSide))
|
override fun call() = subFlow(TestDataVendingFlow(otherSideSession))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.Command
|
import net.corda.core.contracts.Command
|
||||||
import net.corda.core.contracts.StateAndContract
|
import net.corda.core.contracts.StateAndContract
|
||||||
import net.corda.core.contracts.requireThat
|
import net.corda.core.contracts.requireThat
|
||||||
import net.corda.core.identity.AnonymousParty
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -31,7 +30,6 @@ class CollectSignaturesFlowTests {
|
|||||||
lateinit var b: StartedNode<MockNetwork.MockNode>
|
lateinit var b: StartedNode<MockNetwork.MockNode>
|
||||||
lateinit var c: StartedNode<MockNetwork.MockNode>
|
lateinit var c: StartedNode<MockNetwork.MockNode>
|
||||||
lateinit var notary: Party
|
lateinit var notary: Party
|
||||||
val services = MockServices()
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@ -61,12 +59,13 @@ class CollectSignaturesFlowTests {
|
|||||||
// "collectSignaturesFlow" and "SignTransactionFlow" can be used in practise.
|
// "collectSignaturesFlow" and "SignTransactionFlow" can be used in practise.
|
||||||
object TestFlow {
|
object TestFlow {
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class Initiator(val state: DummyContract.MultiOwnerState, val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
class Initiator(private val state: DummyContract.MultiOwnerState, private val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
send(otherParty, state)
|
val session = initiateFlow(otherParty)
|
||||||
|
session.send(state)
|
||||||
|
|
||||||
val flow = object : SignTransactionFlow(otherParty) {
|
val flow = object : SignTransactionFlow(session) {
|
||||||
@Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
@Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
||||||
val tx = stx.tx
|
val tx = stx.tx
|
||||||
val ltx = tx.toLedgerTransaction(serviceHub)
|
val ltx = tx.toLedgerTransaction(serviceHub)
|
||||||
@ -83,19 +82,19 @@ class CollectSignaturesFlowTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(TestFlow.Initiator::class)
|
@InitiatedBy(TestFlow.Initiator::class)
|
||||||
class Responder(val otherParty: Party, val identities: Map<Party, AnonymousParty>) : FlowLogic<SignedTransaction>() {
|
class Responder(private val initiatingSession: FlowSession) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
val state = receive<DummyContract.MultiOwnerState>(otherParty).unwrap { it }
|
val state = initiatingSession.receive<DummyContract.MultiOwnerState>().unwrap { it }
|
||||||
val notary = serviceHub.getDefaultNotary()
|
val notary = serviceHub.getDefaultNotary()
|
||||||
|
|
||||||
val myInputKeys = state.participants.map { it.owningKey }
|
val myInputKeys = state.participants.map { it.owningKey }
|
||||||
val myKeys = myInputKeys + (identities[ourIdentity] ?: ourIdentity).owningKey
|
|
||||||
val command = Command(DummyContract.Commands.Create(), myInputKeys)
|
val command = Command(DummyContract.Commands.Create(), myInputKeys)
|
||||||
val builder = TransactionBuilder(notary).withItems(StateAndContract(state, DUMMY_PROGRAM_ID), command)
|
val builder = TransactionBuilder(notary).withItems(StateAndContract(state, DUMMY_PROGRAM_ID), command)
|
||||||
val ptx = serviceHub.signInitialTransaction(builder)
|
val ptx = serviceHub.signInitialTransaction(builder)
|
||||||
val stx = subFlow(CollectSignaturesFlow(ptx, myKeys))
|
val signature = subFlow(CollectSignatureFlow(ptx, initiatingSession, initiatingSession.counterparty.owningKey))
|
||||||
return subFlow(FinalityFlow(stx)).single()
|
val stx = ptx + signature
|
||||||
|
return subFlow(FinalityFlow(stx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +104,7 @@ class CollectSignaturesFlowTests {
|
|||||||
// receiving off the wire.
|
// receiving off the wire.
|
||||||
object TestFlowTwo {
|
object TestFlowTwo {
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class Initiator(val state: DummyContract.MultiOwnerState) : FlowLogic<SignedTransaction>() {
|
class Initiator(private val state: DummyContract.MultiOwnerState) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
val notary = serviceHub.getDefaultNotary()
|
val notary = serviceHub.getDefaultNotary()
|
||||||
@ -113,15 +112,16 @@ class CollectSignaturesFlowTests {
|
|||||||
val command = Command(DummyContract.Commands.Create(), myInputKeys)
|
val command = Command(DummyContract.Commands.Create(), myInputKeys)
|
||||||
val builder = TransactionBuilder(notary).withItems(StateAndContract(state, DUMMY_PROGRAM_ID), command)
|
val builder = TransactionBuilder(notary).withItems(StateAndContract(state, DUMMY_PROGRAM_ID), command)
|
||||||
val ptx = serviceHub.signInitialTransaction(builder)
|
val ptx = serviceHub.signInitialTransaction(builder)
|
||||||
val stx = subFlow(CollectSignaturesFlow(ptx, myInputKeys))
|
val sessions = serviceHub.excludeMe(serviceHub.groupAbstractPartyByWellKnownParty(state.owners)).map { initiateFlow(it.key) }
|
||||||
return subFlow(FinalityFlow(stx)).single()
|
val stx = subFlow(CollectSignaturesFlow(ptx, sessions, myInputKeys))
|
||||||
|
return subFlow(FinalityFlow(stx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(TestFlowTwo.Initiator::class)
|
@InitiatedBy(TestFlowTwo.Initiator::class)
|
||||||
class Responder(val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
class Responder(private val otherSideSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable override fun call(): SignedTransaction {
|
@Suspendable override fun call() {
|
||||||
val flow = object : SignTransactionFlow(otherParty) {
|
val signFlow = object : SignTransactionFlow(otherSideSession) {
|
||||||
@Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
@Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
||||||
val tx = stx.tx
|
val tx = stx.tx
|
||||||
val ltx = tx.toLedgerTransaction(serviceHub)
|
val ltx = tx.toLedgerTransaction(serviceHub)
|
||||||
@ -132,9 +132,8 @@ class CollectSignaturesFlowTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val stx = subFlow(flow)
|
val stx = subFlow(signFlow)
|
||||||
|
waitForLedgerCommit(stx.id)
|
||||||
return waitForLedgerCommit(stx.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +163,7 @@ class CollectSignaturesFlowTests {
|
|||||||
fun `no need to collect any signatures`() {
|
fun `no need to collect any signatures`() {
|
||||||
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, a.info.chooseIdentity().ref(1))
|
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, a.info.chooseIdentity().ref(1))
|
||||||
val ptx = a.services.signInitialTransaction(onePartyDummyContract)
|
val ptx = a.services.signInitialTransaction(onePartyDummyContract)
|
||||||
val flow = a.services.startFlow(CollectSignaturesFlow(ptx))
|
val flow = a.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val result = flow.resultFuture.getOrThrow()
|
val result = flow.resultFuture.getOrThrow()
|
||||||
result.verifyRequiredSignatures()
|
result.verifyRequiredSignatures()
|
||||||
@ -177,7 +176,7 @@ class CollectSignaturesFlowTests {
|
|||||||
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, a.info.chooseIdentity().ref(1))
|
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, a.info.chooseIdentity().ref(1))
|
||||||
val miniCorpServices = MockServices(MINI_CORP_KEY)
|
val miniCorpServices = MockServices(MINI_CORP_KEY)
|
||||||
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
|
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
|
||||||
val flow = a.services.startFlow(CollectSignaturesFlow(ptx))
|
val flow = a.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertFailsWith<IllegalArgumentException>("The Initiator of CollectSignaturesFlow must have signed the transaction.") {
|
assertFailsWith<IllegalArgumentException>("The Initiator of CollectSignaturesFlow must have signed the transaction.") {
|
||||||
flow.resultFuture.getOrThrow()
|
flow.resultFuture.getOrThrow()
|
||||||
@ -192,7 +191,7 @@ class CollectSignaturesFlowTests {
|
|||||||
b.info.chooseIdentity().ref(3))
|
b.info.chooseIdentity().ref(3))
|
||||||
val signedByA = a.services.signInitialTransaction(twoPartyDummyContract)
|
val signedByA = a.services.signInitialTransaction(twoPartyDummyContract)
|
||||||
val signedByBoth = b.services.addSignature(signedByA)
|
val signedByBoth = b.services.addSignature(signedByA)
|
||||||
val flow = a.services.startFlow(CollectSignaturesFlow(signedByBoth))
|
val flow = a.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet()))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val result = flow.resultFuture.getOrThrow()
|
val result = flow.resultFuture.getOrThrow()
|
||||||
println(result.tx)
|
println(result.tx)
|
||||||
|
@ -21,15 +21,10 @@ import net.corda.node.internal.CordaRPCOpsImpl
|
|||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.RPCDriverExposedDSLInterface
|
import net.corda.testing.*
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.contracts.DummyContractV2
|
import net.corda.testing.contracts.DummyContractV2
|
||||||
import net.corda.testing.getDefaultNotary
|
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
import net.corda.testing.rpcDriver
|
|
||||||
import net.corda.testing.rpcTestUser
|
|
||||||
import net.corda.testing.startRpcClient
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -63,7 +58,6 @@ class ContractUpgradeFlowTest {
|
|||||||
b.database.transaction {
|
b.database.transaction {
|
||||||
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -78,7 +72,7 @@ class ContractUpgradeFlowTest {
|
|||||||
val signedByA = a.services.signInitialTransaction(twoPartyDummyContract)
|
val signedByA = a.services.signInitialTransaction(twoPartyDummyContract)
|
||||||
val stx = b.services.addSignature(signedByA)
|
val stx = b.services.addSignature(signedByA)
|
||||||
|
|
||||||
a.services.startFlow(FinalityFlow(stx, setOf(a.info.chooseIdentity(), b.info.chooseIdentity())))
|
a.services.startFlow(FinalityFlow(stx, setOf(b.info.chooseIdentity())))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
val atx = a.database.transaction { a.services.validatedTransactions.getTransaction(stx.id) }
|
val atx = a.database.transaction { a.services.validatedTransactions.getTransaction(stx.id) }
|
||||||
@ -157,7 +151,7 @@ class ContractUpgradeFlowTest {
|
|||||||
))
|
))
|
||||||
val rpcA = startProxy(a, user)
|
val rpcA = startProxy(a, user)
|
||||||
val rpcB = startProxy(b, user)
|
val rpcB = startProxy(b, user)
|
||||||
val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(a.info.chooseIdentity(), b.info.chooseIdentity()))
|
val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(b.info.chooseIdentity()))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
handle.returnValue.getOrThrow()
|
handle.returnValue.getOrThrow()
|
||||||
|
|
||||||
@ -257,9 +251,9 @@ class ContractUpgradeFlowTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class FinalityInvoker(val transaction: SignedTransaction,
|
class FinalityInvoker(private val transaction: SignedTransaction,
|
||||||
val extraRecipients: Set<Party>) : FlowLogic<List<SignedTransaction>>() {
|
private val extraRecipients: Set<Party>) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): List<SignedTransaction> = subFlow(FinalityFlow(transaction, extraRecipients))
|
override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
package net.corda.core.flows
|
package net.corda.core.flows
|
||||||
|
|
||||||
import net.corda.core.contracts.Amount
|
|
||||||
import net.corda.core.contracts.Issued
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.finance.GBP
|
import net.corda.finance.POUNDS
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.testing.ALICE
|
import net.corda.finance.issuedBy
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.getDefaultNotary
|
import net.corda.testing.getDefaultNotary
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -44,14 +43,13 @@ class FinalityFlowTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `finalise a simple transaction`() {
|
fun `finalise a simple transaction`() {
|
||||||
val amount = Amount(1000, Issued(nodeA.info.chooseIdentity().ref(0), GBP))
|
val amount = 1000.POUNDS.issuedBy(nodeA.info.chooseIdentity().ref(0))
|
||||||
val builder = TransactionBuilder(notary)
|
val builder = TransactionBuilder(notary)
|
||||||
Cash().generateIssue(builder, amount, nodeB.info.chooseIdentity(), notary)
|
Cash().generateIssue(builder, amount, nodeB.info.chooseIdentity(), notary)
|
||||||
val stx = nodeA.services.signInitialTransaction(builder)
|
val stx = nodeA.services.signInitialTransaction(builder)
|
||||||
val flow = nodeA.services.startFlow(FinalityFlow(stx))
|
val flow = nodeA.services.startFlow(FinalityFlow(stx))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val result = flow.resultFuture.getOrThrow()
|
val notarisedTx = flow.resultFuture.getOrThrow()
|
||||||
val notarisedTx = result.single()
|
|
||||||
notarisedTx.verifyRequiredSignatures()
|
notarisedTx.verifyRequiredSignatures()
|
||||||
val transactionSeenByB = nodeB.services.database.transaction {
|
val transactionSeenByB = nodeB.services.database.transaction {
|
||||||
nodeB.services.validatedTransactions.getTransaction(notarisedTx.id)
|
nodeB.services.validatedTransactions.getTransaction(notarisedTx.id)
|
||||||
@ -61,7 +59,7 @@ class FinalityFlowTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reject a transaction with unknown parties`() {
|
fun `reject a transaction with unknown parties`() {
|
||||||
val amount = Amount(1000, Issued(nodeA.info.chooseIdentity().ref(0), GBP))
|
val amount = 1000.POUNDS.issuedBy(nodeA.info.chooseIdentity().ref(0))
|
||||||
val fakeIdentity = ALICE // Alice isn't part of this network, so node A won't recognise them
|
val fakeIdentity = ALICE // Alice isn't part of this network, so node A won't recognise them
|
||||||
val builder = TransactionBuilder(notary)
|
val builder = TransactionBuilder(notary)
|
||||||
Cash().generateIssue(builder, amount, fakeIdentity, notary)
|
Cash().generateIssue(builder, amount, fakeIdentity, notary)
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
package net.corda.core.flows
|
|
||||||
|
|
||||||
import net.corda.core.contracts.Amount
|
|
||||||
import net.corda.core.contracts.Issued
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.finance.GBP
|
|
||||||
import net.corda.finance.contracts.asset.Cash
|
|
||||||
import net.corda.node.internal.StartedNode
|
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.getDefaultNotary
|
|
||||||
import net.corda.testing.node.MockNetwork
|
|
||||||
import net.corda.testing.node.MockServices
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertNull
|
|
||||||
|
|
||||||
class ManualFinalityFlowTests {
|
|
||||||
lateinit var mockNet: MockNetwork
|
|
||||||
lateinit var nodeA: StartedNode<MockNetwork.MockNode>
|
|
||||||
lateinit var nodeB: StartedNode<MockNetwork.MockNode>
|
|
||||||
lateinit var nodeC: StartedNode<MockNetwork.MockNode>
|
|
||||||
lateinit var notary: Party
|
|
||||||
val services = MockServices()
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
mockNet = MockNetwork()
|
|
||||||
val nodes = mockNet.createSomeNodes(3)
|
|
||||||
nodeA = nodes.partyNodes[0]
|
|
||||||
nodeB = nodes.partyNodes[1]
|
|
||||||
nodeC = nodes.partyNodes[2]
|
|
||||||
mockNet.runNetwork()
|
|
||||||
nodeA.internals.ensureRegistered()
|
|
||||||
notary = nodeA.services.getDefaultNotary()
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun tearDown() {
|
|
||||||
mockNet.stopNodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `finalise a simple transaction`() {
|
|
||||||
val amount = Amount(1000, Issued(nodeA.info.chooseIdentity().ref(0), GBP))
|
|
||||||
val builder = TransactionBuilder(notary)
|
|
||||||
Cash().generateIssue(builder, amount, nodeB.info.chooseIdentity(), notary)
|
|
||||||
val stx = nodeA.services.signInitialTransaction(builder)
|
|
||||||
val flow = nodeA.services.startFlow(ManualFinalityFlow(stx, setOf(nodeC.info.chooseIdentity())))
|
|
||||||
mockNet.runNetwork()
|
|
||||||
val result = flow.resultFuture.getOrThrow()
|
|
||||||
val notarisedTx = result.single()
|
|
||||||
notarisedTx.verifyRequiredSignatures()
|
|
||||||
// We override the participants, so node C will get a copy despite not being involved, and B won't
|
|
||||||
val transactionSeenByB = nodeB.services.database.transaction {
|
|
||||||
nodeB.services.validatedTransactions.getTransaction(notarisedTx.id)
|
|
||||||
}
|
|
||||||
assertNull(transactionSeenByB)
|
|
||||||
val transactionSeenByC = nodeC.services.database.transaction {
|
|
||||||
nodeC.services.validatedTransactions.getTransaction(notarisedTx.id)
|
|
||||||
}
|
|
||||||
assertEquals(notarisedTx, transactionSeenByC)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,18 @@
|
|||||||
package net.corda.core.flows
|
package net.corda.core.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.internal.FetchDataFlow
|
import net.corda.core.internal.FetchDataFlow
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
|
|
||||||
// Flow to start data vending without sending transaction. For testing only.
|
// Flow to start data vending without sending transaction. For testing only.
|
||||||
class TestDataVendingFlow(otherSide: Party) : SendStateAndRefFlow(otherSide, emptyList()) {
|
class TestDataVendingFlow(otherSideSession: FlowSession) : SendStateAndRefFlow(otherSideSession, emptyList()) {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
||||||
return if (payload is List<*> && payload.isEmpty()) {
|
return if (payload is List<*> && payload.isEmpty()) {
|
||||||
// Hack to not send the first message.
|
// Hack to not send the first message.
|
||||||
receive(otherSide)
|
otherSideSession.receive()
|
||||||
} else {
|
} else {
|
||||||
super.sendPayloadAndReceiveDataRequest(otherSide, payload)
|
super.sendPayloadAndReceiveDataRequest(this.otherSideSession, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,10 +2,7 @@ package net.corda.core.internal
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.TestDataVendingFlow
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
@ -195,20 +192,22 @@ class ResolveTransactionsFlowTest {
|
|||||||
// DOCEND 2
|
// DOCEND 2
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
private class TestFlow(private val resolveTransactionsFlow: ResolveTransactionsFlow, private val txCountLimit: Int? = null) : FlowLogic<List<SignedTransaction>>() {
|
private class TestFlow(val otherSide: Party, private val resolveTransactionsFlowFactory: (FlowSession) -> ResolveTransactionsFlow, private val txCountLimit: Int? = null) : FlowLogic<List<SignedTransaction>>() {
|
||||||
constructor(txHashes: Set<SecureHash>, otherSide: Party, txCountLimit: Int? = null) : this(ResolveTransactionsFlow(txHashes, otherSide), txCountLimit = txCountLimit)
|
constructor(txHashes: Set<SecureHash>, otherSide: Party, txCountLimit: Int? = null) : this(otherSide, { ResolveTransactionsFlow(txHashes, it) }, txCountLimit = txCountLimit)
|
||||||
constructor(stx: SignedTransaction, otherSide: Party) : this(ResolveTransactionsFlow(stx, otherSide))
|
constructor(stx: SignedTransaction, otherSide: Party) : this(otherSide, { ResolveTransactionsFlow(stx, it) })
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): List<SignedTransaction> {
|
override fun call(): List<SignedTransaction> {
|
||||||
|
val session = initiateFlow(otherSide)
|
||||||
|
val resolveTransactionsFlow = resolveTransactionsFlowFactory(session)
|
||||||
txCountLimit?.let { resolveTransactionsFlow.transactionCountLimit = it }
|
txCountLimit?.let { resolveTransactionsFlow.transactionCountLimit = it }
|
||||||
return subFlow(resolveTransactionsFlow)
|
return subFlow(resolveTransactionsFlow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(TestFlow::class)
|
@InitiatedBy(TestFlow::class)
|
||||||
private class TestResponseFlow(val otherSide: Party) : FlowLogic<Void?>() {
|
private class TestResponseFlow(val otherSideSession: FlowSession) : FlowLogic<Void?>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = subFlow(TestDataVendingFlow(otherSide))
|
override fun call() = subFlow(TestDataVendingFlow(otherSideSession))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.flows.TestDataVendingFlow
|
import net.corda.core.flows.TestDataVendingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
@ -82,14 +83,14 @@ class AttachmentSerializationTest {
|
|||||||
mockNet.stopNodes()
|
mockNet.stopNodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ServerLogic(private val client: Party, private val sendData: Boolean) : FlowLogic<Unit>() {
|
private class ServerLogic(private val clientSession: FlowSession, private val sendData: Boolean) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
if (sendData) {
|
if (sendData) {
|
||||||
subFlow(TestDataVendingFlow(client))
|
subFlow(TestDataVendingFlow(clientSession))
|
||||||
}
|
}
|
||||||
receive<String>(client).unwrap { assertEquals("ping one", it) }
|
clientSession.receive<String>().unwrap { assertEquals("ping one", it) }
|
||||||
sendAndReceive<String>(client, "pong").unwrap { assertEquals("ping two", it) }
|
clientSession.sendAndReceive<String>("pong").unwrap { assertEquals("ping two", it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,9 +101,9 @@ class AttachmentSerializationTest {
|
|||||||
internal val server = server.info.chooseIdentity()
|
internal val server = server.info.chooseIdentity()
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
internal fun communicate() {
|
internal fun communicate(serverSession: FlowSession) {
|
||||||
sendAndReceive<String>(server, "ping one").unwrap { assertEquals("pong", it) }
|
serverSession.sendAndReceive<String>("ping one").unwrap { assertEquals("pong", it) }
|
||||||
send(server, "ping two")
|
serverSession.send("ping two")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -121,7 +122,8 @@ class AttachmentSerializationTest {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun getAttachmentContent(): String {
|
override fun getAttachmentContent(): String {
|
||||||
val customAttachment = CustomAttachment(attachmentId, customContent)
|
val customAttachment = CustomAttachment(attachmentId, customContent)
|
||||||
communicate()
|
val session = initiateFlow(server)
|
||||||
|
communicate(session)
|
||||||
return customAttachment.customContent
|
return customAttachment.customContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,7 +132,8 @@ class AttachmentSerializationTest {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun getAttachmentContent(): String {
|
override fun getAttachmentContent(): String {
|
||||||
val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!!
|
val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!!
|
||||||
communicate()
|
val session = initiateFlow(server)
|
||||||
|
communicate(session)
|
||||||
return localAttachment.extractContent()
|
return localAttachment.extractContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,9 +141,10 @@ class AttachmentSerializationTest {
|
|||||||
private class FetchAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash) : ClientLogic(server) {
|
private class FetchAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash) : ClientLogic(server) {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun getAttachmentContent(): String {
|
override fun getAttachmentContent(): String {
|
||||||
val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), server)).downloaded
|
val serverSession = initiateFlow(server)
|
||||||
send(server, FetchDataFlow.Request.End)
|
val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), serverSession)).downloaded
|
||||||
communicate()
|
serverSession.send(FetchDataFlow.Request.End)
|
||||||
|
communicate(serverSession)
|
||||||
return downloadedAttachment.extractContent()
|
return downloadedAttachment.extractContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +152,7 @@ class AttachmentSerializationTest {
|
|||||||
private fun launchFlow(clientLogic: ClientLogic, rounds: Int, sendData: Boolean = false) {
|
private fun launchFlow(clientLogic: ClientLogic, rounds: Int, sendData: Boolean = false) {
|
||||||
server.internals.internalRegisterFlowFactory(
|
server.internals.internalRegisterFlowFactory(
|
||||||
ClientLogic::class.java,
|
ClientLogic::class.java,
|
||||||
InitiatedFlowFactory.Core { ServerLogic(it.counterparty, sendData) },
|
InitiatedFlowFactory.Core { ServerLogic(it, sendData) },
|
||||||
ServerLogic::class.java,
|
ServerLogic::class.java,
|
||||||
track = false)
|
track = false)
|
||||||
client.services.startFlow(clientLogic)
|
client.services.startFlow(clientLogic)
|
||||||
|
@ -23,7 +23,7 @@ class IntegrationTestingTutorial {
|
|||||||
@Test
|
@Test
|
||||||
fun `alice bob cash exchange example`() {
|
fun `alice bob cash exchange example`() {
|
||||||
// START 1
|
// START 1
|
||||||
driver {
|
driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
||||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
||||||
startFlowPermission<CashIssueFlow>(),
|
startFlowPermission<CashIssueFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>()
|
startFlowPermission<CashPaymentFlow>()
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.docs;
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable;
|
import co.paralleluniverse.fibers.Suspendable;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import net.corda.core.contracts.*;
|
import net.corda.core.contracts.*;
|
||||||
import net.corda.core.crypto.SecureHash;
|
import net.corda.core.crypto.SecureHash;
|
||||||
import net.corda.core.crypto.TransactionSignature;
|
import net.corda.core.crypto.TransactionSignature;
|
||||||
@ -29,6 +28,7 @@ import java.security.GeneralSecurityException;
|
|||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
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
|
// We group our two flows inside a singleton object to indicate that they work
|
||||||
// together.
|
// together.
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class FlowCookbookJava {
|
public class FlowCookbookJava {
|
||||||
// ``InitiatorFlow`` is our first flow, and will communicate with
|
// ``InitiatorFlow`` is our first flow, and will communicate with
|
||||||
// ``ResponderFlow``, below.
|
// ``ResponderFlow``, below.
|
||||||
@ -80,14 +81,14 @@ public class FlowCookbookJava {
|
|||||||
// subflow's progress steps in our flow's progress tracker.
|
// subflow's progress steps in our flow's progress tracker.
|
||||||
@Override
|
@Override
|
||||||
public ProgressTracker childProgressTracker() {
|
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 VERIFYING_SIGS = new Step("Verifying a transaction's signatures.");
|
||||||
private static final Step FINALISATION = new Step("Finalising a transaction.") {
|
private static final Step FINALISATION = new Step("Finalising a transaction.") {
|
||||||
@Override
|
@Override
|
||||||
public ProgressTracker childProgressTracker() {
|
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
|
// registered to respond to this flow, and has a corresponding
|
||||||
// ``receive`` call.
|
// ``receive`` call.
|
||||||
// DOCSTART 4
|
// DOCSTART 4
|
||||||
send(counterparty, new Object());
|
FlowSession counterpartySession = initiateFlow(counterparty);
|
||||||
|
counterpartySession.send(new Object());
|
||||||
// DOCEND 4
|
// DOCEND 4
|
||||||
|
|
||||||
// We can wait to receive arbitrary data of a specific type from a
|
// 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
|
// be what it appears to be! We must unwrap the
|
||||||
// ``UntrustworthyData`` using a lambda.
|
// ``UntrustworthyData`` using a lambda.
|
||||||
// DOCSTART 5
|
// DOCSTART 5
|
||||||
UntrustworthyData<Integer> packet1 = receive(Integer.class, counterparty);
|
UntrustworthyData<Integer> packet1 = counterpartySession.receive(Integer.class);
|
||||||
Integer integer = packet1.unwrap(data -> {
|
Integer integer = packet1.unwrap(data -> {
|
||||||
// Perform checking on the object received.
|
// Perform checking on the object received.
|
||||||
// T O D O: Check the received object.
|
// 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
|
// data sent doesn't need to match the type of the data received
|
||||||
// back.
|
// back.
|
||||||
// DOCSTART 7
|
// 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 -> {
|
Boolean bool = packet2.unwrap(data -> {
|
||||||
// Perform checking on the object received.
|
// Perform checking on the object received.
|
||||||
// T O D O: Check the received object.
|
// 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
|
// counterparty. A flow can send messages to as many parties as it
|
||||||
// likes, and each party can invoke a different response flow.
|
// likes, and each party can invoke a different response flow.
|
||||||
// DOCSTART 6
|
// DOCSTART 6
|
||||||
send(regulator, new Object());
|
FlowSession regulatorSession = initiateFlow(regulator);
|
||||||
UntrustworthyData<Object> packet3 = receive(Object.class, regulator);
|
regulatorSession.send(new Object());
|
||||||
|
UntrustworthyData<Object> packet3 = regulatorSession.receive(Object.class);
|
||||||
// DOCEND 6
|
// DOCEND 6
|
||||||
|
|
||||||
/*------------------------------------
|
/*------------------------------------
|
||||||
@ -395,10 +398,10 @@ public class FlowCookbookJava {
|
|||||||
// for data request until the transaction is resolved and verified
|
// for data request until the transaction is resolved and verified
|
||||||
// on the other side:
|
// on the other side:
|
||||||
// DOCSTART 12
|
// DOCSTART 12
|
||||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx));
|
subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx));
|
||||||
|
|
||||||
// Optional request verification to further restrict data access.
|
// Optional request verification to further restrict data access.
|
||||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx) {
|
subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx) {
|
||||||
@Override
|
@Override
|
||||||
protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
|
protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
|
||||||
// Extra request verification.
|
// Extra request verification.
|
||||||
@ -408,17 +411,17 @@ public class FlowCookbookJava {
|
|||||||
|
|
||||||
// We can receive the transaction using ``ReceiveTransactionFlow``,
|
// We can receive the transaction using ``ReceiveTransactionFlow``,
|
||||||
// which will automatically download all the dependencies and verify
|
// which will automatically download all the dependencies and verify
|
||||||
// the transaction
|
// the transaction and then record in our vault
|
||||||
// DOCSTART 13
|
// DOCSTART 13
|
||||||
SignedTransaction verifiedTransaction = subFlow(new ReceiveTransactionFlow(counterparty));
|
SignedTransaction verifiedTransaction = subFlow(new ReceiveTransactionFlow(counterpartySession));
|
||||||
// DOCEND 13
|
// DOCEND 13
|
||||||
|
|
||||||
// We can also send and receive a `StateAndRef` dependency chain and automatically resolve its dependencies.
|
// We can also send and receive a `StateAndRef` dependency chain and automatically resolve its dependencies.
|
||||||
// DOCSTART 14
|
// DOCSTART 14
|
||||||
subFlow(new SendStateAndRefFlow(counterparty, dummyStates));
|
subFlow(new SendStateAndRefFlow(counterpartySession, dummyStates));
|
||||||
|
|
||||||
// On the receive side ...
|
// On the receive side ...
|
||||||
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(counterparty));
|
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(counterpartySession));
|
||||||
// DOCEND 14
|
// DOCEND 14
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -475,7 +478,7 @@ public class FlowCookbookJava {
|
|||||||
// other required signers using ``CollectSignaturesFlow``.
|
// other required signers using ``CollectSignaturesFlow``.
|
||||||
// The responder flow will need to call ``SignTransactionFlow``.
|
// The responder flow will need to call ``SignTransactionFlow``.
|
||||||
// DOCSTART 15
|
// DOCSTART 15
|
||||||
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()));
|
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, Collections.emptySet(), SIGS_GATHERING.childProgressTracker()));
|
||||||
// DOCEND 15
|
// DOCEND 15
|
||||||
|
|
||||||
/*------------------------
|
/*------------------------
|
||||||
@ -517,13 +520,13 @@ public class FlowCookbookJava {
|
|||||||
// We notarise the transaction and get it recorded in the vault of
|
// We notarise the transaction and get it recorded in the vault of
|
||||||
// the participants of all the transaction's states.
|
// the participants of all the transaction's states.
|
||||||
// DOCSTART 9
|
// DOCSTART 9
|
||||||
SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).get(0);
|
SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker()));
|
||||||
// DOCEND 9
|
// DOCEND 9
|
||||||
// We can also choose to send it to additional parties who aren't one
|
// We can also choose to send it to additional parties who aren't one
|
||||||
// of the state's participants.
|
// of the state's participants.
|
||||||
// DOCSTART 10
|
// DOCSTART 10
|
||||||
Set<Party> additionalParties = ImmutableSet.of(regulator);
|
Set<Party> additionalParties = Collections.singleton(regulator);
|
||||||
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(ImmutableList.of(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).get(0);
|
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker()));
|
||||||
// DOCEND 10
|
// DOCEND 10
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -540,10 +543,10 @@ public class FlowCookbookJava {
|
|||||||
@InitiatedBy(InitiatorFlow.class)
|
@InitiatedBy(InitiatorFlow.class)
|
||||||
public static class ResponderFlow extends FlowLogic<Void> {
|
public static class ResponderFlow extends FlowLogic<Void> {
|
||||||
|
|
||||||
private final Party counterparty;
|
private final FlowSession counterpartySession;
|
||||||
|
|
||||||
public ResponderFlow(Party counterparty) {
|
public ResponderFlow(FlowSession counterpartySession) {
|
||||||
this.counterparty = counterparty;
|
this.counterpartySession = counterpartySession;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Step RECEIVING_AND_SENDING_DATA = new Step("Sending data between parties.");
|
private static final Step RECEIVING_AND_SENDING_DATA = new Step("Sending data between parties.");
|
||||||
@ -575,9 +578,9 @@ public class FlowCookbookJava {
|
|||||||
// ``Boolean`` instance back
|
// ``Boolean`` instance back
|
||||||
// Our side of the flow must mirror these calls.
|
// Our side of the flow must mirror these calls.
|
||||||
// DOCSTART 8
|
// DOCSTART 8
|
||||||
Object obj = receive(Object.class, counterparty).unwrap(data -> data);
|
Object obj = counterpartySession.receive(Object.class).unwrap(data -> data);
|
||||||
String string = sendAndReceive(String.class, counterparty, 99).unwrap(data -> data);
|
String string = counterpartySession.sendAndReceive(String.class, 99).unwrap(data -> data);
|
||||||
send(counterparty, true);
|
counterpartySession.send(true);
|
||||||
// DOCEND 8
|
// DOCEND 8
|
||||||
|
|
||||||
/*-----------------------------------------
|
/*-----------------------------------------
|
||||||
@ -590,8 +593,8 @@ public class FlowCookbookJava {
|
|||||||
// ``SignTransactionFlow`` subclass.
|
// ``SignTransactionFlow`` subclass.
|
||||||
// DOCSTART 16
|
// DOCSTART 16
|
||||||
class SignTxFlow extends SignTransactionFlow {
|
class SignTxFlow extends SignTransactionFlow {
|
||||||
private SignTxFlow(Party otherParty, ProgressTracker progressTracker) {
|
private SignTxFlow(FlowSession otherSession, ProgressTracker progressTracker) {
|
||||||
super(otherParty, progressTracker);
|
super(otherSession, progressTracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -605,7 +608,7 @@ public class FlowCookbookJava {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subFlow(new SignTxFlow(counterparty, SignTransactionFlow.Companion.tracker()));
|
subFlow(new SignTxFlow(counterpartySession, SignTransactionFlow.tracker()));
|
||||||
// DOCEND 16
|
// DOCEND 16
|
||||||
|
|
||||||
/*------------------------------
|
/*------------------------------
|
||||||
|
@ -3,7 +3,6 @@ package net.corda.docs
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.TransactionVerificationException
|
import net.corda.core.contracts.TransactionVerificationException
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
import net.corda.core.node.services.TimeWindowChecker
|
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 timeWindowChecker = TimeWindowChecker(services.clock)
|
||||||
override val uniquenessProvider = PersistentUniquenessProvider()
|
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 start() {}
|
||||||
override fun stop() {}
|
override fun stop() {}
|
||||||
@ -27,7 +26,7 @@ class MyCustomValidatingNotaryService(override val services: ServiceHub, overrid
|
|||||||
// END 1
|
// END 1
|
||||||
|
|
||||||
// START 2
|
// 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
|
* The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole
|
||||||
* transaction dependency chain.
|
* transaction dependency chain.
|
||||||
@ -35,7 +34,7 @@ class MyValidatingNotaryFlow(otherSide: Party, service: MyCustomValidatingNotary
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun receiveAndVerifyTx(): TransactionParts {
|
override fun receiveAndVerifyTx(): TransactionParts {
|
||||||
try {
|
try {
|
||||||
val stx = subFlow(ReceiveTransactionFlow(otherSide, checkSufficientSignatures = false))
|
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
|
||||||
checkNotary(stx.notary)
|
checkNotary(stx.notary)
|
||||||
checkSignatures(stx)
|
checkSignatures(stx)
|
||||||
val wtx = stx.tx
|
val wtx = stx.tx
|
||||||
|
@ -2,10 +2,7 @@ package net.corda.docs
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.StartableByRPC
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
@ -85,12 +82,12 @@ object TopupIssuerFlow {
|
|||||||
@Throws(CashException::class)
|
@Throws(CashException::class)
|
||||||
override fun call(): List<AbstractCashFlow.Result> {
|
override fun call(): List<AbstractCashFlow.Result> {
|
||||||
val topupRequest = TopupRequest(issueToParty, issueToPartyRef, notaryParty)
|
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)
|
@InitiatedBy(TopupIssuanceRequester::class)
|
||||||
class TopupIssuer(val otherParty: Party) : FlowLogic<List<SignedTransaction>>() {
|
class TopupIssuer(val otherPartySession: FlowSession) : FlowLogic<List<SignedTransaction>>() {
|
||||||
companion object {
|
companion object {
|
||||||
object AWAITING_REQUEST : ProgressTracker.Step("Awaiting issuance request")
|
object AWAITING_REQUEST : ProgressTracker.Step("Awaiting issuance request")
|
||||||
object ISSUING : ProgressTracker.Step("Issuing asset")
|
object ISSUING : ProgressTracker.Step("Issuing asset")
|
||||||
@ -107,7 +104,7 @@ object TopupIssuerFlow {
|
|||||||
@Throws(CashException::class)
|
@Throws(CashException::class)
|
||||||
override fun call(): List<SignedTransaction> {
|
override fun call(): List<SignedTransaction> {
|
||||||
progressTracker.currentStep = AWAITING_REQUEST
|
progressTracker.currentStep = AWAITING_REQUEST
|
||||||
val topupRequest = receive<TopupRequest>(otherParty).unwrap {
|
val topupRequest = otherPartySession.receive<TopupRequest>().unwrap {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +119,7 @@ object TopupIssuerFlow {
|
|||||||
return@map txn.stx
|
return@map txn.stx
|
||||||
}
|
}
|
||||||
|
|
||||||
send(otherParty, txns)
|
otherPartySession.send(txns)
|
||||||
return txns
|
return txns
|
||||||
}
|
}
|
||||||
// DOCEND TopupIssuer
|
// 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.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
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.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.finance.contracts.asset.Cash
|
||||||
import net.corda.testing.ALICE_PUBKEY
|
import net.corda.testing.ALICE_PUBKEY
|
||||||
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
|
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
|
||||||
@ -39,7 +42,7 @@ object FlowCookbook {
|
|||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
// Every flow must subclass ``FlowLogic``. The generic indicates the
|
// Every flow must subclass ``FlowLogic``. The generic indicates the
|
||||||
// flow's return type.
|
// 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 *
|
* WIRING UP THE PROGRESS TRACKER *
|
||||||
@ -135,7 +138,8 @@ object FlowCookbook {
|
|||||||
// registered to respond to this flow, and has a corresponding
|
// registered to respond to this flow, and has a corresponding
|
||||||
// ``receive`` call.
|
// ``receive`` call.
|
||||||
// DOCSTART 4
|
// DOCSTART 4
|
||||||
send(counterparty, Any())
|
val counterpartySession = initiateFlow(counterparty)
|
||||||
|
counterpartySession.send(Any())
|
||||||
// DOCEND 4
|
// DOCEND 4
|
||||||
|
|
||||||
// We can wait to receive arbitrary data of a specific type from a
|
// 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
|
// be what it appears to be! We must unwrap the
|
||||||
// ``UntrustworthyData`` using a lambda.
|
// ``UntrustworthyData`` using a lambda.
|
||||||
// DOCSTART 5
|
// DOCSTART 5
|
||||||
val packet1: UntrustworthyData<Int> = receive<Int>(counterparty)
|
val packet1: UntrustworthyData<Int> = counterpartySession.receive<Int>()
|
||||||
val int: Int = packet1.unwrap { data ->
|
val int: Int = packet1.unwrap { data ->
|
||||||
// Perform checking on the object received.
|
// Perform checking on the object received.
|
||||||
// T O D O: Check the received object.
|
// 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
|
// data sent doesn't need to match the type of the data received
|
||||||
// back.
|
// back.
|
||||||
// DOCSTART 7
|
// 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 ->
|
val boolean: Boolean = packet2.unwrap { data ->
|
||||||
// Perform checking on the object received.
|
// Perform checking on the object received.
|
||||||
// T O D O: Check the received object.
|
// 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
|
// counterparty. A flow can send messages to as many parties as it
|
||||||
// likes, and each party can invoke a different response flow.
|
// likes, and each party can invoke a different response flow.
|
||||||
// DOCSTART 6
|
// DOCSTART 6
|
||||||
send(regulator, Any())
|
val regulatorSession = initiateFlow(regulator)
|
||||||
val packet3: UntrustworthyData<Any> = receive<Any>(regulator)
|
regulatorSession.send(Any())
|
||||||
|
val packet3: UntrustworthyData<Any> = regulatorSession.receive<Any>()
|
||||||
// DOCEND 6
|
// DOCEND 6
|
||||||
|
|
||||||
/**-----------------------------------
|
/**-----------------------------------
|
||||||
@ -378,10 +383,10 @@ object FlowCookbook {
|
|||||||
// for data request until the transaction is resolved and verified
|
// for data request until the transaction is resolved and verified
|
||||||
// on the other side:
|
// on the other side:
|
||||||
// DOCSTART 12
|
// DOCSTART 12
|
||||||
subFlow(SendTransactionFlow(counterparty, twiceSignedTx))
|
subFlow(SendTransactionFlow(counterpartySession, twiceSignedTx))
|
||||||
|
|
||||||
// Optional request verification to further restrict data access.
|
// 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) {
|
override fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) {
|
||||||
// Extra request verification.
|
// Extra request verification.
|
||||||
}
|
}
|
||||||
@ -392,16 +397,16 @@ object FlowCookbook {
|
|||||||
// which will automatically download all the dependencies and verify
|
// which will automatically download all the dependencies and verify
|
||||||
// the transaction
|
// the transaction
|
||||||
// DOCSTART 13
|
// DOCSTART 13
|
||||||
val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterparty))
|
val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterpartySession))
|
||||||
// DOCEND 13
|
// DOCEND 13
|
||||||
|
|
||||||
// We can also send and receive a `StateAndRef` dependency chain
|
// We can also send and receive a `StateAndRef` dependency chain
|
||||||
// and automatically resolve its dependencies.
|
// and automatically resolve its dependencies.
|
||||||
// DOCSTART 14
|
// DOCSTART 14
|
||||||
subFlow(SendStateAndRefFlow(counterparty, dummyStates))
|
subFlow(SendStateAndRefFlow(counterpartySession, dummyStates))
|
||||||
|
|
||||||
// On the receive side ...
|
// On the receive side ...
|
||||||
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(counterparty))
|
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(counterpartySession))
|
||||||
// DOCEND 14
|
// DOCEND 14
|
||||||
|
|
||||||
// We can now verify the transaction to ensure that it satisfies
|
// We can now verify the transaction to ensure that it satisfies
|
||||||
@ -452,7 +457,7 @@ object FlowCookbook {
|
|||||||
// other required signers using ``CollectSignaturesFlow``.
|
// other required signers using ``CollectSignaturesFlow``.
|
||||||
// The responder flow will need to call ``SignTransactionFlow``.
|
// The responder flow will need to call ``SignTransactionFlow``.
|
||||||
// DOCSTART 15
|
// DOCSTART 15
|
||||||
val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()))
|
val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, emptySet(), SIGS_GATHERING.childProgressTracker()))
|
||||||
// DOCEND 15
|
// DOCEND 15
|
||||||
|
|
||||||
/**-----------------------
|
/**-----------------------
|
||||||
@ -488,13 +493,13 @@ object FlowCookbook {
|
|||||||
// We notarise the transaction and get it recorded in the vault of
|
// We notarise the transaction and get it recorded in the vault of
|
||||||
// the participants of all the transaction's states.
|
// the participants of all the transaction's states.
|
||||||
// DOCSTART 9
|
// DOCSTART 9
|
||||||
val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).single()
|
val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker()))
|
||||||
// DOCEND 9
|
// DOCEND 9
|
||||||
// We can also choose to send it to additional parties who aren't one
|
// We can also choose to send it to additional parties who aren't one
|
||||||
// of the state's participants.
|
// of the state's participants.
|
||||||
// DOCSTART 10
|
// DOCSTART 10
|
||||||
val additionalParties: Set<Party> = setOf(regulator)
|
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
|
// DOCEND 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -507,7 +512,7 @@ object FlowCookbook {
|
|||||||
// Each node also has several flow pairs registered by default - see
|
// Each node also has several flow pairs registered by default - see
|
||||||
// ``AbstractNode.installCoreFlows``.
|
// ``AbstractNode.installCoreFlows``.
|
||||||
@InitiatedBy(InitiatorFlow::class)
|
@InitiatedBy(InitiatorFlow::class)
|
||||||
class ResponderFlow(val counterparty: Party) : FlowLogic<Unit>() {
|
class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.")
|
object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.")
|
||||||
@ -541,9 +546,9 @@ object FlowCookbook {
|
|||||||
// ``Boolean`` instance back
|
// ``Boolean`` instance back
|
||||||
// Our side of the flow must mirror these calls.
|
// Our side of the flow must mirror these calls.
|
||||||
// DOCSTART 8
|
// DOCSTART 8
|
||||||
val any: Any = receive<Any>(counterparty).unwrap { data -> data }
|
val any: Any = counterpartySession.receive<Any>().unwrap { data -> data }
|
||||||
val string: String = sendAndReceive<String>(counterparty, 99).unwrap { data -> data }
|
val string: String = counterpartySession.sendAndReceive<String>(99).unwrap { data -> data }
|
||||||
send(counterparty, true)
|
counterpartySession.send(true)
|
||||||
// DOCEND 8
|
// DOCEND 8
|
||||||
|
|
||||||
/**----------------------------------------
|
/**----------------------------------------
|
||||||
@ -555,7 +560,7 @@ object FlowCookbook {
|
|||||||
// ``CollectSignaturesFlow``. It does so my invoking its own
|
// ``CollectSignaturesFlow``. It does so my invoking its own
|
||||||
// ``SignTransactionFlow`` subclass.
|
// ``SignTransactionFlow`` subclass.
|
||||||
// DOCSTART 16
|
// DOCSTART 16
|
||||||
val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterparty) {
|
val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterpartySession) {
|
||||||
override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
||||||
// Any additional checking we see fit...
|
// Any additional checking we see fit...
|
||||||
val outputState = stx.tx.outputsOfType<DummyState>().single()
|
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)
|
val eligibleStates = serviceHub.vaultService.tryLockFungibleStatesForSpending(lockId, fullCriteria, amountRequired.withoutIssuer(), Cash.State::class.java)
|
||||||
|
|
||||||
check(eligibleStates.isNotEmpty()) { "Insufficient funds" }
|
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
|
val change = amount - amountRequired.quantity
|
||||||
|
|
||||||
return Pair(eligibleStates, change)
|
return Pair(eligibleStates, change)
|
||||||
@ -87,25 +87,25 @@ private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, lockId: UUID, req
|
|||||||
// A flow representing creating a transaction that
|
// A flow representing creating a transaction that
|
||||||
// carries out exchange of cash assets.
|
// carries out exchange of cash assets.
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class ForeignExchangeFlow(val tradeId: String,
|
class ForeignExchangeFlow(private val tradeId: String,
|
||||||
val baseCurrencyAmount: Amount<Issued<Currency>>,
|
private val baseCurrencyAmount: Amount<Issued<Currency>>,
|
||||||
val quoteCurrencyAmount: Amount<Issued<Currency>>,
|
private val quoteCurrencyAmount: Amount<Issued<Currency>>,
|
||||||
val baseCurrencyBuyer: Party,
|
private val counterparty: Party,
|
||||||
val baseCurrencySeller: Party) : FlowLogic<SecureHash>() {
|
private val weAreBaseCurrencySeller: Boolean) : FlowLogic<SecureHash>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SecureHash {
|
override fun call(): SecureHash {
|
||||||
// Select correct sides of the Fx exchange to query for.
|
// Select correct sides of the Fx exchange to query for.
|
||||||
// Specifically we own the assets we wish to sell.
|
// Specifically we own the assets we wish to sell.
|
||||||
// Also prepare the other side query
|
// Also prepare the other side query
|
||||||
val (localRequest, remoteRequest) = if (serviceHub.myInfo.isLegalIdentity(baseCurrencySeller)) {
|
val (localRequest, remoteRequest) = if (weAreBaseCurrencySeller) {
|
||||||
val local = FxRequest(tradeId, baseCurrencyAmount, baseCurrencySeller, baseCurrencyBuyer)
|
val local = FxRequest(tradeId, baseCurrencyAmount, ourIdentity, counterparty)
|
||||||
val remote = FxRequest(tradeId, quoteCurrencyAmount, baseCurrencyBuyer, baseCurrencySeller)
|
val remote = FxRequest(tradeId, quoteCurrencyAmount, counterparty, ourIdentity)
|
||||||
Pair(local, remote)
|
Pair(local, remote)
|
||||||
} else if (serviceHub.myInfo.isLegalIdentity(baseCurrencyBuyer)) {
|
} else {
|
||||||
val local = FxRequest(tradeId, quoteCurrencyAmount, baseCurrencyBuyer, baseCurrencySeller)
|
val local = FxRequest(tradeId, quoteCurrencyAmount, ourIdentity, counterparty)
|
||||||
val remote = FxRequest(tradeId, baseCurrencyAmount, baseCurrencySeller, baseCurrencyBuyer)
|
val remote = FxRequest(tradeId, baseCurrencyAmount, counterparty, ourIdentity)
|
||||||
Pair(local, remote)
|
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
|
// Call the helper method to identify suitable inputs and make the outputs
|
||||||
val (ourInputStates, ourOutputStates) = prepareOurInputsAndOutputs(serviceHub, runId.uuid, localRequest)
|
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
|
// Send the request to the counterparty to verify and call their version of prepareOurInputsAndOutputs
|
||||||
// Then they can return their candidate states
|
// Then they can return their candidate states
|
||||||
send(remoteRequestWithNotary.owner, remoteRequestWithNotary)
|
val counterpartySession = initiateFlow(counterparty)
|
||||||
val theirInputStates = subFlow(ReceiveStateAndRefFlow<Cash.State>(remoteRequestWithNotary.owner))
|
counterpartySession.send(remoteRequestWithNotary)
|
||||||
val theirOutputStates = receive<List<Cash.State>>(remoteRequestWithNotary.owner).unwrap {
|
val theirInputStates = subFlow(ReceiveStateAndRefFlow<Cash.State>(counterpartySession))
|
||||||
|
val theirOutputStates = counterpartySession.receive<List<Cash.State>>().unwrap {
|
||||||
require(theirInputStates.all { it.state.notary == notary }) {
|
require(theirInputStates.all { it.state.notary == notary }) {
|
||||||
"notary of remote states must be same as for our states"
|
"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)
|
val signedTransaction = buildTradeProposal(ourInputStates, ourOutputStates, theirInputStates, theirOutputStates)
|
||||||
|
|
||||||
// pass transaction details to the counterparty to revalidate and confirm with a signature
|
// pass transaction details to the counterparty to revalidate and confirm with a signature
|
||||||
// Allow otherParty to access our data to resolve the transaction.
|
// Allow counterparty to access our data to resolve the transaction.
|
||||||
subFlow(SendTransactionFlow(remoteRequestWithNotary.owner, signedTransaction))
|
subFlow(SendTransactionFlow(counterpartySession, signedTransaction))
|
||||||
val allPartySignedTx = receive<TransactionSignature>(remoteRequestWithNotary.owner).unwrap {
|
val allPartySignedTx = counterpartySession.receive<TransactionSignature>().unwrap {
|
||||||
val withNewSignature = signedTransaction + it
|
val withNewSignature = signedTransaction + it
|
||||||
// check all signatures are present except the notary
|
// check all signatures are present except the notary
|
||||||
withNewSignature.verifySignaturesExcept(withNewSignature.tx.notary!!.owningKey)
|
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.
|
// 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
|
return allPartySignedTx.id
|
||||||
}
|
}
|
||||||
@ -195,11 +196,11 @@ class ForeignExchangeFlow(val tradeId: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(ForeignExchangeFlow::class)
|
@InitiatedBy(ForeignExchangeFlow::class)
|
||||||
class ForeignExchangeRemoteFlow(private val source: Party) : FlowLogic<Unit>() {
|
class ForeignExchangeRemoteFlow(private val source: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
// Initial receive from remote party
|
// 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!
|
// 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.
|
// Also that the amounts and source are correct with the trade details.
|
||||||
// In a production system there would be other Corda contracts tracking
|
// 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)) {
|
require(serviceHub.myInfo.isLegalIdentity(it.owner)) {
|
||||||
"Request does not include the correct counterparty"
|
"Request does not include the correct counterparty"
|
||||||
}
|
}
|
||||||
require(source == it.counterparty) {
|
require(source.counterparty == it.counterparty) {
|
||||||
"Request does not include the correct counterparty"
|
"Request does not include the correct counterparty"
|
||||||
}
|
}
|
||||||
it // return validated request
|
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
|
// 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()
|
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))
|
subFlow(SendStateAndRefFlow(source, ourInputState))
|
||||||
send(source, ourOutputState)
|
source.send(ourOutputState)
|
||||||
val proposedTrade = subFlow(ReceiveTransactionFlow(source, checkSufficientSignatures = false)).let {
|
val proposedTrade = subFlow(ReceiveTransactionFlow(source, checkSufficientSignatures = false)).let {
|
||||||
val wtx = it.tx
|
val wtx = it.tx
|
||||||
// check all signatures are present except our own and the notary
|
// check all signatures are present except our own and the notary
|
||||||
it.verifySignaturesExcept(ourKey, wtx.notary!!.owningKey)
|
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
|
it // return the SignedTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +239,7 @@ class ForeignExchangeRemoteFlow(private val source: Party) : FlowLogic<Unit>() {
|
|||||||
val ourSignature = serviceHub.createSignature(proposedTrade, ourKey)
|
val ourSignature = serviceHub.createSignature(proposedTrade, ourKey)
|
||||||
|
|
||||||
// send the other side our signature.
|
// send the other side our signature.
|
||||||
send(source, ourSignature)
|
source.send(ourSignature)
|
||||||
// N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction
|
// N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction
|
||||||
// and broadcasting the result to us.
|
// and broadcasting the result to us.
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@ package net.corda.docs
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.flows.FinalityFlow
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.queryBy
|
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.
|
// We can automatically sign as there is no untrusted data.
|
||||||
val signedTx = serviceHub.signInitialTransaction(tx)
|
val signedTx = serviceHub.signInitialTransaction(tx)
|
||||||
// Notarise and distribute.
|
// Notarise and distribute.
|
||||||
subFlow(FinalityFlow(signedTx, setOf(ourIdentity, counterparty)))
|
subFlow(FinalityFlow(signedTx, setOf(counterparty)))
|
||||||
// Return the initial state
|
// Return the initial state
|
||||||
return signedTx.tx.outRef(0)
|
return signedTx.tx.outRef(0)
|
||||||
}
|
}
|
||||||
@ -175,7 +172,8 @@ class SubmitCompletionFlow(private val ref: StateRef, private val verdict: Workf
|
|||||||
val selfSignedTx = serviceHub.signInitialTransaction(tx)
|
val selfSignedTx = serviceHub.signInitialTransaction(tx)
|
||||||
//DOCEND 2
|
//DOCEND 2
|
||||||
// Send the signed transaction to the originator and await their signature to confirm
|
// 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.
|
// Add their signature to our unmodified transaction. To check they signed the same tx.
|
||||||
val agreedTx = selfSignedTx + it
|
val agreedTx = selfSignedTx + it
|
||||||
// Receive back their signature and confirm that it is for an unmodified transaction
|
// 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
|
// DOCSTART 4
|
||||||
// Notarise and distribute the completed transaction.
|
// 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
|
// DOCEND 4
|
||||||
// Return back the details of the completed state/transaction.
|
// Return back the details of the completed state/transaction.
|
||||||
return allPartySignedTx.tx.outRef(0)
|
return allPartySignedTx.tx.outRef(0)
|
||||||
@ -202,12 +200,12 @@ class SubmitCompletionFlow(private val ref: StateRef, private val verdict: Workf
|
|||||||
* transaction to the ledger.
|
* transaction to the ledger.
|
||||||
*/
|
*/
|
||||||
@InitiatedBy(SubmitCompletionFlow::class)
|
@InitiatedBy(SubmitCompletionFlow::class)
|
||||||
class RecordCompletionFlow(private val source: Party) : FlowLogic<Unit>() {
|
class RecordCompletionFlow(private val sourceSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
// DOCSTART 3
|
// DOCSTART 3
|
||||||
// First we receive the verdict transaction signed by their single key
|
// 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
|
// Check the transaction is signed apart from our own key and the notary
|
||||||
it.verifySignaturesExcept(ourIdentity.owningKey, it.tx.notary!!.owningKey)
|
it.verifySignaturesExcept(ourIdentity.owningKey, it.tx.notary!!.owningKey)
|
||||||
// Check the transaction data is correctly formed
|
// 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)) {
|
require(serviceHub.myInfo.isLegalIdentity(state.state.data.source)) {
|
||||||
"Proposal not one of our original proposals"
|
"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"
|
"Proposal not for sent from correct source"
|
||||||
}
|
}
|
||||||
it
|
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
|
// Having verified the SignedTransaction passed to us we can sign it too
|
||||||
val ourSignature = serviceHub.createSignature(completeTx)
|
val ourSignature = serviceHub.createSignature(completeTx)
|
||||||
// Send our signature to the other party.
|
// Send our signature to the other party.
|
||||||
send(source, ourSignature)
|
sourceSession.send(ourSignature)
|
||||||
// N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction
|
// N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction
|
||||||
// and broadcasting the result to us.
|
// and broadcasting the result to us.
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,11 @@ import net.corda.core.utilities.getOrThrow
|
|||||||
import net.corda.finance.*
|
import net.corda.finance.*
|
||||||
import net.corda.finance.contracts.getCashBalances
|
import net.corda.finance.contracts.getCashBalances
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.node.internal.StartedNode
|
|
||||||
import net.corda.finance.schemas.CashSchemaV1
|
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.network.NetworkMapService
|
||||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||||
|
import net.corda.nodeapi.ServiceInfo
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.DUMMY_NOTARY_KEY
|
import net.corda.testing.DUMMY_NOTARY_KEY
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
@ -76,8 +76,8 @@ class FxTransactionBuildTutorialTest {
|
|||||||
val doIt = nodeA.services.startFlow(ForeignExchangeFlow("trade1",
|
val doIt = nodeA.services.startFlow(ForeignExchangeFlow("trade1",
|
||||||
POUNDS(100).issuedBy(nodeB.info.chooseIdentity().ref(0x01)),
|
POUNDS(100).issuedBy(nodeB.info.chooseIdentity().ref(0x01)),
|
||||||
DOLLARS(200).issuedBy(nodeA.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
|
// wait for the flow to finish and the vault updates to be done
|
||||||
doIt.resultFuture.getOrThrow()
|
doIt.resultFuture.getOrThrow()
|
||||||
// Get the balances when the vault updates
|
// 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.toFuture
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.nodeapi.ServiceInfo
|
|
||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||||
|
import net.corda.nodeapi.ServiceInfo
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.DUMMY_NOTARY_KEY
|
import net.corda.testing.DUMMY_NOTARY_KEY
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
|
@ -27,9 +27,9 @@ abstract class AbstractCashFlow<out T>(override val progressTracker: ProgressTra
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected fun finaliseTx(participants: Set<Party>, tx: SignedTransaction, message: String) {
|
protected fun finaliseTx(tx: SignedTransaction, extraParticipants: Set<Party>, message: String): SignedTransaction {
|
||||||
try {
|
try {
|
||||||
subFlow(FinalityFlow(tx, participants))
|
return subFlow(FinalityFlow(tx, extraParticipants))
|
||||||
} catch (e: NotaryException) {
|
} catch (e: NotaryException) {
|
||||||
throw CashException(message, e)
|
throw CashException(message, e)
|
||||||
}
|
}
|
||||||
|
@ -44,9 +44,10 @@ class CashExitFlow(private val amount: Amount<Currency>,
|
|||||||
@Throws(CashException::class)
|
@Throws(CashException::class)
|
||||||
override fun call(): AbstractCashFlow.Result {
|
override fun call(): AbstractCashFlow.Result {
|
||||||
progressTracker.currentStep = GENERATING_TX
|
progressTracker.currentStep = GENERATING_TX
|
||||||
val builder = TransactionBuilder(notary = null as Party?)
|
val builder = TransactionBuilder(notary = null)
|
||||||
val issuer = ourIdentity.ref(issuerRef)
|
val issuer = ourIdentity.ref(issuerRef)
|
||||||
val exitStates = CashSelection.getInstance { serviceHub.jdbcSession().metaData }
|
val exitStates = CashSelection
|
||||||
|
.getInstance { serviceHub.jdbcSession().metaData }
|
||||||
.unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
|
.unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
|
||||||
val signers = try {
|
val signers = try {
|
||||||
Cash().generateExit(
|
Cash().generateExit(
|
||||||
@ -72,8 +73,8 @@ class CashExitFlow(private val amount: Amount<Currency>,
|
|||||||
|
|
||||||
// Commit the transaction
|
// Commit the transaction
|
||||||
progressTracker.currentStep = FINALISING_TX
|
progressTracker.currentStep = FINALISING_TX
|
||||||
finaliseTx(participants, tx, "Unable to notarise exit")
|
val notarised = finaliseTx(tx, participants, "Unable to notarise exit")
|
||||||
return Result(tx, null)
|
return Result(notarised, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.finance.flows
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.flows.FinalityFlow
|
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -43,7 +42,8 @@ class CashIssueFlow(private val amount: Amount<Currency>,
|
|||||||
progressTracker.currentStep = SIGNING_TX
|
progressTracker.currentStep = SIGNING_TX
|
||||||
val tx = serviceHub.signInitialTransaction(builder, signers)
|
val tx = serviceHub.signInitialTransaction(builder, signers)
|
||||||
progressTracker.currentStep = FINALISING_TX
|
progressTracker.currentStep = FINALISING_TX
|
||||||
val notarised = subFlow(FinalityFlow(tx)).single()
|
// There is no one to send the tx to as we're the only participants
|
||||||
|
val notarised = finaliseTx(tx, emptySet(), "Unable to notarise issue")
|
||||||
return Result(notarised, ourIdentity)
|
return Result(notarised, ourIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ open class CashPaymentFlow(
|
|||||||
}
|
}
|
||||||
val anonymousRecipient = txIdentities[recipient] ?: recipient
|
val anonymousRecipient = txIdentities[recipient] ?: recipient
|
||||||
progressTracker.currentStep = GENERATING_TX
|
progressTracker.currentStep = GENERATING_TX
|
||||||
val builder = TransactionBuilder(null as Party?)
|
val builder = TransactionBuilder(notary = null)
|
||||||
// TODO: Have some way of restricting this to states the caller controls
|
// TODO: Have some way of restricting this to states the caller controls
|
||||||
val (spendTX, keysForSigning) = try {
|
val (spendTX, keysForSigning) = try {
|
||||||
Cash.generateSpend(serviceHub,
|
Cash.generateSpend(serviceHub,
|
||||||
@ -61,10 +61,13 @@ open class CashPaymentFlow(
|
|||||||
val tx = serviceHub.signInitialTransaction(spendTX, keysForSigning)
|
val tx = serviceHub.signInitialTransaction(spendTX, keysForSigning)
|
||||||
|
|
||||||
progressTracker.currentStep = FINALISING_TX
|
progressTracker.currentStep = FINALISING_TX
|
||||||
finaliseTx(setOf(recipient), tx, "Unable to notarise spend")
|
val notarised = finaliseTx(tx, setOf(recipient), "Unable to notarise spend")
|
||||||
return Result(tx, anonymousRecipient)
|
return Result(notarised, anonymousRecipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class PaymentRequest(amount: Amount<Currency>, val recipient: Party, val anonymous: Boolean, val issuerConstraint: Set<Party> = emptySet()) : AbstractRequest(amount)
|
class PaymentRequest(amount: Amount<Currency>,
|
||||||
}
|
val recipient: Party,
|
||||||
|
val anonymous: Boolean,
|
||||||
|
val issuerConstraint: Set<Party> = emptySet()) : AbstractRequest(amount)
|
||||||
|
}
|
||||||
|
@ -3,12 +3,8 @@ package net.corda.finance.flows
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.confidential.SwapIdentitiesFlow
|
import net.corda.confidential.SwapIdentitiesFlow
|
||||||
import net.corda.core.contracts.requireThat
|
import net.corda.core.contracts.requireThat
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.flows.CollectSignaturesFlow
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.FinalityFlow
|
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.flows.SignTransactionFlow
|
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -50,38 +46,38 @@ object TwoPartyDealFlow {
|
|||||||
|
|
||||||
abstract val payload: Any
|
abstract val payload: Any
|
||||||
abstract val notaryParty: Party
|
abstract val notaryParty: Party
|
||||||
abstract val otherParty: Party
|
abstract val otherSideSession: FlowSession
|
||||||
|
|
||||||
@Suspendable override fun call(): SignedTransaction {
|
@Suspendable
|
||||||
|
override fun call(): SignedTransaction {
|
||||||
progressTracker.currentStep = GENERATING_ID
|
progressTracker.currentStep = GENERATING_ID
|
||||||
val txIdentities = subFlow(SwapIdentitiesFlow(otherParty))
|
val txIdentities = subFlow(SwapIdentitiesFlow(otherSideSession.counterparty))
|
||||||
val anonymousMe = txIdentities[ourIdentity] ?: ourIdentity.anonymise()
|
val anonymousMe = txIdentities[ourIdentity] ?: ourIdentity.anonymise()
|
||||||
val anonymousCounterparty = txIdentities[otherParty] ?: otherParty.anonymise()
|
val anonymousCounterparty = txIdentities[otherSideSession.counterparty] ?: otherSideSession.counterparty.anonymise()
|
||||||
progressTracker.currentStep = SENDING_PROPOSAL
|
progressTracker.currentStep = SENDING_PROPOSAL
|
||||||
// Make the first message we'll send to kick off the flow.
|
// Make the first message we'll send to kick off the flow.
|
||||||
val hello = Handshake(payload, anonymousMe, anonymousCounterparty)
|
val hello = Handshake(payload, anonymousMe, anonymousCounterparty)
|
||||||
// Wait for the FinalityFlow to finish on the other side and return the tx when it's available.
|
// Wait for the FinalityFlow to finish on the other side and return the tx when it's available.
|
||||||
send(otherParty, hello)
|
otherSideSession.send(hello)
|
||||||
|
|
||||||
val signTransactionFlow = object : SignTransactionFlow(otherParty) {
|
val signTransactionFlow = object : SignTransactionFlow(otherSideSession) {
|
||||||
override fun checkTransaction(stx: SignedTransaction) = checkProposal(stx)
|
override fun checkTransaction(stx: SignedTransaction) = checkProposal(stx)
|
||||||
}
|
}
|
||||||
|
|
||||||
subFlow(signTransactionFlow)
|
val txId = subFlow(signTransactionFlow).id
|
||||||
|
|
||||||
val txHash = receive<SecureHash>(otherParty).unwrap { it }
|
return waitForLedgerCommit(txId)
|
||||||
|
|
||||||
return waitForLedgerCommit(txHash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable abstract fun checkProposal(stx: SignedTransaction)
|
@Suspendable
|
||||||
|
abstract fun checkProposal(stx: SignedTransaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstracted bilateral deal flow participant that is recipient of initial communication.
|
* Abstracted bilateral deal flow participant that is recipient of initial communication.
|
||||||
*/
|
*/
|
||||||
abstract class Secondary<U>(override val progressTracker: ProgressTracker = Secondary.tracker(),
|
abstract class Secondary<U>(override val progressTracker: ProgressTracker = Secondary.tracker(),
|
||||||
val regulators: List<Party> = emptyList()) : FlowLogic<SignedTransaction>() {
|
val regulators: Set<Party> = emptySet()) : FlowLogic<SignedTransaction>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
object RECEIVING : ProgressTracker.Step("Waiting for deal info.")
|
object RECEIVING : ProgressTracker.Step("Waiting for deal info.")
|
||||||
@ -89,13 +85,11 @@ object TwoPartyDealFlow {
|
|||||||
object SIGNING : ProgressTracker.Step("Generating and signing transaction proposal.")
|
object SIGNING : ProgressTracker.Step("Generating and signing transaction proposal.")
|
||||||
object COLLECTING_SIGNATURES : ProgressTracker.Step("Collecting signatures from other parties.")
|
object COLLECTING_SIGNATURES : ProgressTracker.Step("Collecting signatures from other parties.")
|
||||||
object RECORDING : ProgressTracker.Step("Recording completed transaction.")
|
object RECORDING : ProgressTracker.Step("Recording completed transaction.")
|
||||||
object COPYING_TO_REGULATOR : ProgressTracker.Step("Copying regulator.")
|
|
||||||
object COPYING_TO_COUNTERPARTY : ProgressTracker.Step("Copying counterparty.")
|
|
||||||
|
|
||||||
fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING, COLLECTING_SIGNATURES, RECORDING, COPYING_TO_REGULATOR, COPYING_TO_COUNTERPARTY)
|
fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING, COLLECTING_SIGNATURES, RECORDING)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract val otherParty: Party
|
abstract val otherSideSession: FlowSession
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
@ -109,31 +103,24 @@ object TwoPartyDealFlow {
|
|||||||
serviceHub.signInitialTransaction(utx, additionalSigningPubKeys)
|
serviceHub.signInitialTransaction(utx, additionalSigningPubKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace { "Signed proposed transaction." }
|
logger.trace("Signed proposed transaction.")
|
||||||
|
|
||||||
progressTracker.currentStep = COLLECTING_SIGNATURES
|
progressTracker.currentStep = COLLECTING_SIGNATURES
|
||||||
|
|
||||||
|
// Get signature of initiating side
|
||||||
|
val ptxSignedByOtherSide = ptx + subFlow(CollectSignatureFlow(ptx, otherSideSession, otherSideSession.counterparty.owningKey))
|
||||||
|
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
val stx = subFlow(CollectSignaturesFlow(ptx, additionalSigningPubKeys))
|
// Get signatures of other signers
|
||||||
|
val sessionsForOtherSigners = serviceHub.excludeNotary(serviceHub.groupPublicKeysByWellKnownParty(ptxSignedByOtherSide.getMissingSigners()), ptxSignedByOtherSide).map { initiateFlow(it.key) }
|
||||||
|
val stx = subFlow(CollectSignaturesFlow(ptxSignedByOtherSide, sessionsForOtherSigners, additionalSigningPubKeys))
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
|
|
||||||
logger.trace { "Got signatures from other party, verifying ... " }
|
logger.trace("Got signatures from other party, verifying ... ")
|
||||||
|
|
||||||
progressTracker.currentStep = RECORDING
|
progressTracker.currentStep = RECORDING
|
||||||
val ftx = subFlow(FinalityFlow(stx, setOf(otherParty, ourIdentity))).single()
|
val ftx = subFlow(FinalityFlow(stx, regulators + otherSideSession.counterparty))
|
||||||
|
logger.trace("Recorded transaction.")
|
||||||
logger.trace { "Recorded transaction." }
|
|
||||||
|
|
||||||
progressTracker.currentStep = COPYING_TO_REGULATOR
|
|
||||||
|
|
||||||
// Copy the transaction to every regulator in the network. This is obviously completely bogus, it's
|
|
||||||
// just for demo purposes.
|
|
||||||
regulators.forEach { send(it, ftx) }
|
|
||||||
|
|
||||||
progressTracker.currentStep = COPYING_TO_COUNTERPARTY
|
|
||||||
// Send the final transaction hash back to the other party.
|
|
||||||
// We need this so we don't break the IRS demo and the SIMM Demo.
|
|
||||||
send(otherParty, ftx.id)
|
|
||||||
|
|
||||||
return ftx
|
return ftx
|
||||||
}
|
}
|
||||||
@ -142,14 +129,14 @@ object TwoPartyDealFlow {
|
|||||||
private fun receiveAndValidateHandshake(): Handshake<U> {
|
private fun receiveAndValidateHandshake(): Handshake<U> {
|
||||||
progressTracker.currentStep = RECEIVING
|
progressTracker.currentStep = RECEIVING
|
||||||
// Wait for a trade request to come in on our pre-provided session ID.
|
// Wait for a trade request to come in on our pre-provided session ID.
|
||||||
val handshake = receive<Handshake<U>>(otherParty)
|
val handshake = otherSideSession.receive<Handshake<U>>()
|
||||||
|
|
||||||
progressTracker.currentStep = VERIFYING
|
progressTracker.currentStep = VERIFYING
|
||||||
return handshake.unwrap {
|
return handshake.unwrap {
|
||||||
// Verify the transaction identities represent the correct parties
|
// Verify the transaction identities represent the correct parties
|
||||||
val wellKnownOtherParty = serviceHub.identityService.partyFromAnonymous(it.primaryIdentity)
|
val wellKnownOtherParty = serviceHub.identityService.partyFromAnonymous(it.primaryIdentity)
|
||||||
val wellKnownMe = serviceHub.identityService.partyFromAnonymous(it.secondaryIdentity)
|
val wellKnownMe = serviceHub.identityService.partyFromAnonymous(it.secondaryIdentity)
|
||||||
require(wellKnownOtherParty == otherParty)
|
require(wellKnownOtherParty == otherSideSession.counterparty)
|
||||||
require(wellKnownMe == ourIdentity)
|
require(wellKnownMe == ourIdentity)
|
||||||
validateHandshake(it)
|
validateHandshake(it)
|
||||||
}
|
}
|
||||||
@ -167,7 +154,7 @@ object TwoPartyDealFlow {
|
|||||||
/**
|
/**
|
||||||
* One side of the flow for inserting a pre-agreed deal.
|
* One side of the flow for inserting a pre-agreed deal.
|
||||||
*/
|
*/
|
||||||
open class Instigator(override val otherParty: Party,
|
open class Instigator(override val otherSideSession: FlowSession,
|
||||||
override val payload: AutoOffer,
|
override val payload: AutoOffer,
|
||||||
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary() {
|
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary() {
|
||||||
override val notaryParty: Party get() = payload.notary
|
override val notaryParty: Party get() = payload.notary
|
||||||
@ -180,7 +167,7 @@ object TwoPartyDealFlow {
|
|||||||
/**
|
/**
|
||||||
* One side of the flow for inserting a pre-agreed deal.
|
* One side of the flow for inserting a pre-agreed deal.
|
||||||
*/
|
*/
|
||||||
open class Acceptor(override val otherParty: Party,
|
open class Acceptor(override val otherSideSession: FlowSession,
|
||||||
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<AutoOffer>() {
|
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<AutoOffer>() {
|
||||||
|
|
||||||
override fun validateHandshake(handshake: Handshake<AutoOffer>): Handshake<AutoOffer> {
|
override fun validateHandshake(handshake: Handshake<AutoOffer>): Handshake<AutoOffer> {
|
||||||
|
@ -55,10 +55,10 @@ object TwoPartyTradeFlow {
|
|||||||
val payToIdentity: PartyAndCertificate
|
val payToIdentity: PartyAndCertificate
|
||||||
)
|
)
|
||||||
|
|
||||||
open class Seller(val otherParty: Party,
|
open class Seller(private val otherSideSession: FlowSession,
|
||||||
val assetToSell: StateAndRef<OwnableState>,
|
private val assetToSell: StateAndRef<OwnableState>,
|
||||||
val price: Amount<Currency>,
|
private val price: Amount<Currency>,
|
||||||
val myParty: PartyAndCertificate, // TODO Left because in tests it's used to pass anonymous party.
|
private val myParty: PartyAndCertificate, // TODO Left because in tests it's used to pass anonymous party.
|
||||||
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
|
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -80,22 +80,22 @@ object TwoPartyTradeFlow {
|
|||||||
val hello = SellerTradeInfo(price, myParty)
|
val hello = SellerTradeInfo(price, myParty)
|
||||||
// What we get back from the other side is a transaction that *might* be valid and acceptable to us,
|
// What we get back from the other side is a transaction that *might* be valid and acceptable to us,
|
||||||
// but we must check it out thoroughly before we sign!
|
// but we must check it out thoroughly before we sign!
|
||||||
// SendTransactionFlow allows otherParty to access our data to resolve the transaction.
|
// SendTransactionFlow allows seller to access our data to resolve the transaction.
|
||||||
subFlow(SendStateAndRefFlow(otherParty, listOf(assetToSell)))
|
subFlow(SendStateAndRefFlow(otherSideSession, listOf(assetToSell)))
|
||||||
send(otherParty, hello)
|
otherSideSession.send(hello)
|
||||||
|
|
||||||
// Verify and sign the transaction.
|
// Verify and sign the transaction.
|
||||||
progressTracker.currentStep = VERIFYING_AND_SIGNING
|
progressTracker.currentStep = VERIFYING_AND_SIGNING
|
||||||
|
|
||||||
// Sync identities to ensure we know all of the identities involved in the transaction we're about to
|
// Sync identities to ensure we know all of the identities involved in the transaction we're about to
|
||||||
// be asked to sign
|
// be asked to sign
|
||||||
subFlow(IdentitySyncFlow.Receive(otherParty))
|
subFlow(IdentitySyncFlow.Receive(otherSideSession))
|
||||||
|
|
||||||
// DOCSTART 5
|
// DOCSTART 5
|
||||||
val signTransactionFlow = object : SignTransactionFlow(otherParty, VERIFYING_AND_SIGNING.childProgressTracker()) {
|
val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) {
|
||||||
override fun checkTransaction(stx: SignedTransaction) {
|
override fun checkTransaction(stx: SignedTransaction) {
|
||||||
// Verify that we know who all the participants in the transaction are
|
// Verify that we know who all the participants in the transaction are
|
||||||
val states: Iterable<ContractState> = (stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data })
|
val states: Iterable<ContractState> = stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data }
|
||||||
states.forEach { state ->
|
states.forEach { state ->
|
||||||
state.participants.forEach { anon ->
|
state.participants.forEach { anon ->
|
||||||
require(serviceHub.identityService.partyFromAnonymous(anon) != null) {
|
require(serviceHub.identityService.partyFromAnonymous(anon) != null) {
|
||||||
@ -108,8 +108,11 @@ object TwoPartyTradeFlow {
|
|||||||
throw FlowException("Transaction is not sending us the right amount of cash")
|
throw FlowException("Transaction is not sending us the right amount of cash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subFlow(signTransactionFlow)
|
|
||||||
|
val txId = subFlow(signTransactionFlow).id
|
||||||
// DOCEND 5
|
// DOCEND 5
|
||||||
|
|
||||||
|
return waitForLedgerCommit(txId)
|
||||||
}
|
}
|
||||||
// DOCEND 4
|
// DOCEND 4
|
||||||
|
|
||||||
@ -126,13 +129,13 @@ object TwoPartyTradeFlow {
|
|||||||
// express flow state machines on top of the messaging layer.
|
// express flow state machines on top of the messaging layer.
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Buyer(private val otherParty: Party,
|
open class Buyer(private val sellerSession: FlowSession,
|
||||||
private val notary: Party,
|
private val notary: Party,
|
||||||
private val acceptablePrice: Amount<Currency>,
|
private val acceptablePrice: Amount<Currency>,
|
||||||
private val typeToBuy: Class<out OwnableState>,
|
private val typeToBuy: Class<out OwnableState>,
|
||||||
private val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
|
private val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
|
||||||
constructor(otherParty: Party, notary: Party, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>) :
|
constructor(otherSideSession: FlowSession, notary: Party, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>) :
|
||||||
this(otherParty, notary, acceptablePrice, typeToBuy, true)
|
this(otherSideSession, notary, acceptablePrice, typeToBuy, true)
|
||||||
// DOCSTART 2
|
// DOCSTART 2
|
||||||
object RECEIVING : ProgressTracker.Step("Waiting for seller trading info")
|
object RECEIVING : ProgressTracker.Step("Waiting for seller trading info")
|
||||||
|
|
||||||
@ -170,22 +173,23 @@ object TwoPartyTradeFlow {
|
|||||||
val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
|
val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
|
||||||
|
|
||||||
// Sync up confidential identities in the transaction with our counterparty
|
// Sync up confidential identities in the transaction with our counterparty
|
||||||
subFlow(IdentitySyncFlow.Send(otherParty, ptx.toWireTransaction()))
|
subFlow(IdentitySyncFlow.Send(sellerSession, ptx.toWireTransaction()))
|
||||||
|
|
||||||
// Send the signed transaction to the seller, who must then sign it themselves and commit
|
// Send the signed transaction to the seller, who must then sign it themselves and commit
|
||||||
// it to the ledger by sending it to the notary.
|
// it to the ledger by sending it to the notary.
|
||||||
progressTracker.currentStep = COLLECTING_SIGNATURES
|
progressTracker.currentStep = COLLECTING_SIGNATURES
|
||||||
val twiceSignedTx = subFlow(CollectSignaturesFlow(partSignedTx, cashSigningPubKeys, COLLECTING_SIGNATURES.childProgressTracker()))
|
val sellerSignature = subFlow(CollectSignatureFlow(partSignedTx, sellerSession, sellerSession.counterparty.owningKey))
|
||||||
|
val twiceSignedTx = partSignedTx + sellerSignature
|
||||||
|
|
||||||
// Notarise and record the transaction.
|
// Notarise and record the transaction.
|
||||||
progressTracker.currentStep = RECORDING
|
progressTracker.currentStep = RECORDING
|
||||||
return subFlow(FinalityFlow(twiceSignedTx)).single()
|
return subFlow(FinalityFlow(twiceSignedTx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun receiveAndValidateTradeRequest(): Pair<StateAndRef<OwnableState>, SellerTradeInfo> {
|
private fun receiveAndValidateTradeRequest(): Pair<StateAndRef<OwnableState>, SellerTradeInfo> {
|
||||||
val assetForSale = subFlow(ReceiveStateAndRefFlow<OwnableState>(otherParty)).single()
|
val assetForSale = subFlow(ReceiveStateAndRefFlow<OwnableState>(sellerSession)).single()
|
||||||
return assetForSale to receive<SellerTradeInfo>(otherParty).unwrap {
|
return assetForSale to sellerSession.receive<SellerTradeInfo>().unwrap {
|
||||||
progressTracker.currentStep = VERIFYING
|
progressTracker.currentStep = VERIFYING
|
||||||
// What is the seller trying to sell us?
|
// What is the seller trying to sell us?
|
||||||
val asset = assetForSale.state.data
|
val asset = assetForSale.state.data
|
||||||
@ -194,12 +198,12 @@ object TwoPartyTradeFlow {
|
|||||||
// The asset must either be owned by the well known identity of the counterparty, or we must be able to
|
// The asset must either be owned by the well known identity of the counterparty, or we must be able to
|
||||||
// prove the owner is a confidential identity of the counterparty.
|
// prove the owner is a confidential identity of the counterparty.
|
||||||
val assetForSaleIdentity = serviceHub.identityService.partyFromAnonymous(asset.owner)
|
val assetForSaleIdentity = serviceHub.identityService.partyFromAnonymous(asset.owner)
|
||||||
require(assetForSaleIdentity == otherParty)
|
require(assetForSaleIdentity == sellerSession.counterparty)
|
||||||
|
|
||||||
// Register the identity we're about to send payment to. This shouldn't be the same as the asset owner
|
// Register the identity we're about to send payment to. This shouldn't be the same as the asset owner
|
||||||
// identity, so that anonymity is enforced.
|
// identity, so that anonymity is enforced.
|
||||||
val wellKnownPayToIdentity = serviceHub.identityService.verifyAndRegisterIdentity(it.payToIdentity)
|
val wellKnownPayToIdentity = serviceHub.identityService.verifyAndRegisterIdentity(it.payToIdentity)
|
||||||
require(wellKnownPayToIdentity?.party == otherParty) { "Well known identity to pay to must match counterparty identity" }
|
require(wellKnownPayToIdentity?.party == sellerSession.counterparty) { "Well known identity to pay to must match counterparty identity" }
|
||||||
|
|
||||||
if (it.price > acceptablePrice)
|
if (it.price > acceptablePrice)
|
||||||
throw UnacceptablePriceException(it.price)
|
throw UnacceptablePriceException(it.price)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.finance.flows;
|
package net.corda.finance.flows;
|
||||||
|
|
||||||
import net.corda.core.flows.AbstractStateReplacementFlow;
|
import net.corda.core.flows.AbstractStateReplacementFlow;
|
||||||
import net.corda.core.identity.Party;
|
import net.corda.core.flows.FlowSession;
|
||||||
import net.corda.core.transactions.SignedTransaction;
|
import net.corda.core.transactions.SignedTransaction;
|
||||||
import net.corda.core.utilities.ProgressTracker;
|
import net.corda.core.utilities.ProgressTracker;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -11,8 +11,8 @@ public class AbstractStateReplacementFlowTest {
|
|||||||
|
|
||||||
// Acceptor used to have a type parameter of Unit which prevented Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964).
|
// Acceptor used to have a type parameter of Unit which prevented Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964).
|
||||||
private static class TestAcceptorCanBeInheritedInJava extends AbstractStateReplacementFlow.Acceptor {
|
private static class TestAcceptorCanBeInheritedInJava extends AbstractStateReplacementFlow.Acceptor {
|
||||||
public TestAcceptorCanBeInheritedInJava(@NotNull Party otherSide, @NotNull ProgressTracker progressTracker) {
|
public TestAcceptorCanBeInheritedInJava(@NotNull FlowSession otherSideSession, @NotNull ProgressTracker progressTracker) {
|
||||||
super(otherSide, progressTracker);
|
super(otherSideSession, progressTracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,10 +38,9 @@ class CashPaymentFlowTests {
|
|||||||
notaryNode = nodes.notaryNode
|
notaryNode = nodes.notaryNode
|
||||||
bankOfCordaNode = nodes.partyNodes[0]
|
bankOfCordaNode = nodes.partyNodes[0]
|
||||||
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
|
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
|
||||||
|
notary = notaryNode.services.getDefaultNotary()
|
||||||
mockNet.runNetwork()
|
|
||||||
notary = bankOfCordaNode.services.getDefaultNotary()
|
|
||||||
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture
|
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture
|
||||||
|
mockNet.runNetwork()
|
||||||
future.getOrThrow()
|
future.getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.StartableByRPC
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.testing.ALICE
|
|
||||||
import net.corda.testing.BOB
|
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
|
import net.corda.testing.ALICE
|
||||||
|
import net.corda.testing.BOB
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@ -41,15 +38,15 @@ class CordappScanningDriverTest {
|
|||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class ReceiveFlow(val otherParty: Party) :FlowLogic<String>() {
|
class ReceiveFlow(val otherParty: Party) :FlowLogic<String>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): String = receive<String>(otherParty).unwrap { it }
|
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(ReceiveFlow::class)
|
@InitiatedBy(ReceiveFlow::class)
|
||||||
open class SendClassFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
open class SendClassFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = send(otherParty, javaClass.name)
|
override fun call() = otherPartySession.send(javaClass.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(ReceiveFlow::class)
|
@InitiatedBy(ReceiveFlow::class)
|
||||||
class SendSubClassFlow(otherParty: Party) : SendClassFlow(otherParty)
|
class SendSubClassFlow(otherPartySession: FlowSession) : SendClassFlow(otherPartySession)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.node.services.statemachine
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
@ -32,17 +33,18 @@ class FlowVersioningTest : NodeBasedTest() {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Pair<Int, Int> {
|
override fun call(): Pair<Int, Int> {
|
||||||
// Execute receive() outside of the Pair constructor to avoid Kotlin/Quasar instrumentation bug.
|
// Execute receive() outside of the Pair constructor to avoid Kotlin/Quasar instrumentation bug.
|
||||||
val alicePlatformVersionAccordingToBob = receive<Int>(initiatedParty).unwrap { it }
|
val session = initiateFlow(initiatedParty)
|
||||||
|
val alicePlatformVersionAccordingToBob = session.receive<Int>().unwrap { it }
|
||||||
return Pair(
|
return Pair(
|
||||||
alicePlatformVersionAccordingToBob,
|
alicePlatformVersionAccordingToBob,
|
||||||
getFlowInfo(initiatedParty).flowVersion
|
session.getCounterpartyFlowInfo().flowVersion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PretendInitiatedCoreFlow(val initiatingParty: Party) : FlowLogic<Unit>() {
|
private class PretendInitiatedCoreFlow(val otherSideSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = send(initiatingParty, getFlowInfo(initiatingParty).flowVersion)
|
override fun call() = otherSideSession.send(otherSideSession.getCounterpartyFlowInfo().flowVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -3,7 +3,6 @@ package net.corda.node.services.statemachine
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.internal.InputStreamAndHash
|
import net.corda.core.internal.InputStreamAndHash
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -39,18 +38,19 @@ class LargeTransactionsTest {
|
|||||||
val stx = serviceHub.signInitialTransaction(tx, ourIdentity.owningKey)
|
val stx = serviceHub.signInitialTransaction(tx, ourIdentity.owningKey)
|
||||||
// Send to the other side and wait for it to trigger resolution from us.
|
// Send to the other side and wait for it to trigger resolution from us.
|
||||||
val bob = serviceHub.identityService.partyFromX500Name(BOB.name)!!
|
val bob = serviceHub.identityService.partyFromX500Name(BOB.name)!!
|
||||||
subFlow(SendTransactionFlow(bob, stx))
|
val bobSession = initiateFlow(bob)
|
||||||
receive<Unit>(bob)
|
subFlow(SendTransactionFlow(bobSession, stx))
|
||||||
|
bobSession.receive<Unit>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(SendLargeTransactionFlow::class) @Suppress("UNUSED")
|
@InitiatedBy(SendLargeTransactionFlow::class) @Suppress("UNUSED")
|
||||||
class ReceiveLargeTransactionFlow(private val counterParty: Party) : FlowLogic<Unit>() {
|
class ReceiveLargeTransactionFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
subFlow(ReceiveTransactionFlow(counterParty))
|
subFlow(ReceiveTransactionFlow(otherSide))
|
||||||
// Unblock the other side by sending some dummy object (Unit is fine here as it's a singleton).
|
// Unblock the other side by sending some dummy object (Unit is fine here as it's a singleton).
|
||||||
send(counterParty, Unit)
|
otherSide.send(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,17 +3,16 @@ package net.corda.services.messaging
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.utilities.toBase58String
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.crypto.random63BitValue
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.testing.ALICE
|
import net.corda.core.utilities.toBase58String
|
||||||
import net.corda.testing.BOB
|
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
@ -25,6 +24,8 @@ import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
|||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.nodeapi.config.SSLConfiguration
|
import net.corda.nodeapi.config.SSLConfiguration
|
||||||
|
import net.corda.testing.ALICE
|
||||||
|
import net.corda.testing.BOB
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.configureTestSSL
|
import net.corda.testing.configureTestSSL
|
||||||
import net.corda.testing.messaging.SimpleMQClient
|
import net.corda.testing.messaging.SimpleMQClient
|
||||||
@ -229,12 +230,12 @@ abstract class MQSecurityTest : NodeBasedTest() {
|
|||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
private class SendFlow(val otherParty: Party, val payload: Any) : FlowLogic<Unit>() {
|
private class SendFlow(val otherParty: Party, val payload: Any) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = send(otherParty, payload)
|
override fun call() = initiateFlow(otherParty).send(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(SendFlow::class)
|
@InitiatedBy(SendFlow::class)
|
||||||
private class ReceiveFlow(val otherParty: Party) : FlowLogic<Any>() {
|
private class ReceiveFlow(val otherPartySession: FlowSession) : FlowLogic<Any>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = receive<Any>(otherParty).unwrap { it }
|
override fun call() = otherPartySession.receive<Any>().unwrap { it }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,8 +18,8 @@ import net.corda.core.transactions.TransactionBuilder
|
|||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.services.FlowPermissions
|
import net.corda.node.services.FlowPermissions
|
||||||
import net.corda.nodeapi.ServiceInfo
|
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
|
import net.corda.nodeapi.ServiceInfo
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
@ -151,6 +151,6 @@ class SendMessageFlow(private val message: Message) : FlowLogic<SignedTransactio
|
|||||||
val signedTx = serviceHub.signInitialTransaction(txBuilder)
|
val signedTx = serviceHub.signInitialTransaction(txBuilder)
|
||||||
|
|
||||||
progressTracker.currentStep = FINALISING_TRANSACTION
|
progressTracker.currentStep = FINALISING_TRANSACTION
|
||||||
return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker())).single()
|
return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker()))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -36,8 +36,8 @@ import net.corda.core.utilities.debug
|
|||||||
import net.corda.node.internal.classloading.requireAnnotation
|
import net.corda.node.internal.classloading.requireAnnotation
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import net.corda.node.internal.cordapp.CordappProvider
|
import net.corda.node.internal.cordapp.CordappProvider
|
||||||
|
import net.corda.node.services.FinalityHandler
|
||||||
import net.corda.node.services.NotaryChangeHandler
|
import net.corda.node.services.NotaryChangeHandler
|
||||||
import net.corda.node.services.NotifyTransactionHandler
|
|
||||||
import net.corda.node.services.api.*
|
import net.corda.node.services.api.*
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||||
@ -360,15 +360,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
* compatibility [flowFactory] provides a second parameter which is the platform version of the initiating party.
|
* compatibility [flowFactory] provides a second parameter which is the platform version of the initiating party.
|
||||||
* @suppress
|
* @suppress
|
||||||
*/
|
*/
|
||||||
@Deprecated("Use installCoreFlowExpectingFlowSession() instead")
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
fun installCoreFlow(clientFlowClass: KClass<out FlowLogic<*>>, flowFactory: (Party) -> FlowLogic<*>) {
|
fun installCoreFlow(clientFlowClass: KClass<out FlowLogic<*>>, flowFactory: (FlowSession) -> FlowLogic<*>) {
|
||||||
log.warn(deprecatedFlowConstructorMessage(clientFlowClass.java))
|
|
||||||
installCoreFlowExpectingFlowSession(clientFlowClass, { flowSession -> flowFactory(flowSession.counterparty) })
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
fun installCoreFlowExpectingFlowSession(clientFlowClass: KClass<out FlowLogic<*>>, flowFactory: (FlowSession) -> FlowLogic<*>) {
|
|
||||||
require(clientFlowClass.java.flowVersionAndInitiatingClass.first == 1) {
|
require(clientFlowClass.java.flowVersionAndInitiatingClass.first == 1) {
|
||||||
"${InitiatingFlow::class.java.name}.version not applicable for core flows; their version is the node's platform version"
|
"${InitiatingFlow::class.java.name}.version not applicable for core flows; their version is the node's platform version"
|
||||||
}
|
}
|
||||||
@ -378,7 +371,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
|
|
||||||
private fun installCoreFlows() {
|
private fun installCoreFlows() {
|
||||||
installCoreFlow(BroadcastTransactionFlow::class, ::NotifyTransactionHandler)
|
installCoreFlow(FinalityFlow::class, ::FinalityHandler)
|
||||||
installCoreFlow(NotaryChangeFlow::class, ::NotaryChangeHandler)
|
installCoreFlow(NotaryChangeFlow::class, ::NotaryChangeHandler)
|
||||||
installCoreFlow(ContractUpgradeFlow.Initiator::class, ::Acceptor)
|
installCoreFlow(ContractUpgradeFlow.Initiator::class, ::Acceptor)
|
||||||
installCoreFlow(SwapIdentitiesFlow::class, ::SwapIdentitiesHandler)
|
installCoreFlow(SwapIdentitiesFlow::class, ::SwapIdentitiesHandler)
|
||||||
@ -407,10 +400,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun makeCordappLoader(): CordappLoader {
|
private fun makeCordappLoader(): CordappLoader {
|
||||||
val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package")
|
val scanPackages = System.getProperty("net.corda.node.cordapp.scan.packages")
|
||||||
return if (scanPackage != null) {
|
return if (scanPackages != null) {
|
||||||
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
|
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
|
||||||
CordappLoader.createDevMode(scanPackage)
|
CordappLoader.createDevMode(scanPackages)
|
||||||
} else {
|
} else {
|
||||||
CordappLoader.createDefault(configuration.baseDirectory)
|
CordappLoader.createDefault(configuration.baseDirectory)
|
||||||
}
|
}
|
||||||
|
@ -57,19 +57,22 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
|||||||
* @param scanPackage Resolves the JARs that contain scanPackage and use them as the source for
|
* @param scanPackage Resolves the JARs that contain scanPackage and use them as the source for
|
||||||
* the classpath scanning.
|
* the classpath scanning.
|
||||||
*/
|
*/
|
||||||
fun createDevMode(scanPackage: String): CordappLoader {
|
fun createDevMode(scanPackages: String): CordappLoader {
|
||||||
val resource = scanPackage.replace('.', '/')
|
val paths = scanPackages.split(",").flatMap { scanPackage ->
|
||||||
val paths = this::class.java.classLoader.getResources(resource)
|
val resource = scanPackage.replace('.', '/')
|
||||||
.asSequence()
|
this::class.java.classLoader.getResources(resource)
|
||||||
.map {
|
.asSequence()
|
||||||
val uri = if (it.protocol == "jar") {
|
.map {
|
||||||
(it.openConnection() as JarURLConnection).jarFileURL.toURI()
|
val uri = if (it.protocol == "jar") {
|
||||||
} else {
|
(it.openConnection() as JarURLConnection).jarFileURL.toURI()
|
||||||
URI(it.toExternalForm().removeSuffix(resource))
|
} else {
|
||||||
|
URI(it.toExternalForm().removeSuffix(resource))
|
||||||
|
}
|
||||||
|
uri.toURL()
|
||||||
}
|
}
|
||||||
uri.toURL()
|
.toList()
|
||||||
}
|
}
|
||||||
.toList()
|
|
||||||
return CordappLoader(paths)
|
return CordappLoader(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package net.corda.node.services
|
package net.corda.node.services
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.AbstractStateReplacementFlow
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.flows.ReceiveTransactionFlow
|
|
||||||
import net.corda.core.flows.StateReplacementException
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
|
||||||
@ -12,15 +9,15 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
// includes us in any outside that list. Potentially just if it includes any outside that list at all.
|
// includes us in any outside that list. Potentially just if it includes any outside that list at all.
|
||||||
// TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming
|
// TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming
|
||||||
// cash without from unknown parties?
|
// cash without from unknown parties?
|
||||||
class NotifyTransactionHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
class FinalityHandler(private val sender: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val stx = subFlow(ReceiveTransactionFlow(otherParty))
|
val stx = subFlow(ReceiveTransactionFlow(sender))
|
||||||
serviceHub.recordTransactions(stx)
|
serviceHub.recordTransactions(stx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotaryChangeHandler(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Party>(otherSide) {
|
class NotaryChangeHandler(otherSideSession: FlowSession) : AbstractStateReplacementFlow.Acceptor<Party>(otherSideSession) {
|
||||||
/**
|
/**
|
||||||
* Check the notary change proposal.
|
* Check the notary change proposal.
|
||||||
*
|
*
|
||||||
|
@ -18,7 +18,7 @@ class FlowSessionInternal(
|
|||||||
val ourSessionId: Long,
|
val ourSessionId: Long,
|
||||||
val initiatingParty: Party?,
|
val initiatingParty: Party?,
|
||||||
var state: FlowSessionState,
|
var state: FlowSessionState,
|
||||||
val retryable: Boolean = false) {
|
var retryable: Boolean = false) {
|
||||||
val receivedMessages = ConcurrentLinkedQueue<ReceivedSessionMessage<*>>()
|
val receivedMessages = ConcurrentLinkedQueue<ReceivedSessionMessage<*>>()
|
||||||
val fiber: FlowStateMachineImpl<*> get() = flow.stateMachine as FlowStateMachineImpl<*>
|
val fiber: FlowStateMachineImpl<*> get() = flow.stateMachine as FlowStateMachineImpl<*>
|
||||||
|
|
||||||
@ -30,14 +30,19 @@ class FlowSessionInternal(
|
|||||||
/**
|
/**
|
||||||
* [FlowSessionState] describes the session's state.
|
* [FlowSessionState] describes the session's state.
|
||||||
*
|
*
|
||||||
* [Initiating] is pre-handshake. [Initiating.otherParty] at this point holds a [Party] corresponding to either a
|
* [Uninitiated] is pre-handshake, where no communication has happened. [Initiating.otherParty] at this point holds a
|
||||||
* specific peer or a service.
|
* [Party] corresponding to either a specific peer or a service.
|
||||||
|
* [Initiating] is pre-handshake, where the initiating message has been sent.
|
||||||
* [Initiated] is post-handshake. At this point [Initiating.otherParty] will have been resolved to a specific peer
|
* [Initiated] is post-handshake. At this point [Initiating.otherParty] will have been resolved to a specific peer
|
||||||
* [Initiated.peerParty], and the peer's sessionId has been initialised.
|
* [Initiated.peerParty], and the peer's sessionId has been initialised.
|
||||||
*/
|
*/
|
||||||
sealed class FlowSessionState {
|
sealed class FlowSessionState {
|
||||||
abstract val sendToParty: Party
|
abstract val sendToParty: Party
|
||||||
|
|
||||||
|
data class Uninitiated(val otherParty: Party) : FlowSessionState() {
|
||||||
|
override val sendToParty: Party get() = otherParty
|
||||||
|
}
|
||||||
|
|
||||||
/** [otherParty] may be a specific peer or a service party */
|
/** [otherParty] may be a specific peer or a service party */
|
||||||
data class Initiating(val otherParty: Party) : FlowSessionState() {
|
data class Initiating(val otherParty: Party) : FlowSessionState() {
|
||||||
override val sendToParty: Party get() = otherParty
|
override val sendToParty: Party get() = otherParty
|
||||||
|
@ -164,6 +164,16 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun initiateFlow(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSession {
|
override fun initiateFlow(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSession {
|
||||||
|
val sessionKey = Pair(sessionFlow, otherParty)
|
||||||
|
if (openSessions.containsKey(sessionKey)) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Attempted to initiateFlow() twice in the same InitiatingFlow $sessionFlow for the same party " +
|
||||||
|
"$otherParty. This isn't supported in this version of Corda. Alternatively you may " +
|
||||||
|
"initiate a new flow by calling initiateFlow() in an " +
|
||||||
|
"@${InitiatingFlow::class.java.simpleName} sub-flow."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
createNewSession(otherParty, sessionFlow)
|
||||||
val flowSession = FlowSessionImpl(otherParty)
|
val flowSession = FlowSessionImpl(otherParty)
|
||||||
flowSession.stateMachine = this
|
flowSession.stateMachine = this
|
||||||
flowSession.sessionFlow = sessionFlow
|
flowSession.sessionFlow = sessionFlow
|
||||||
@ -186,7 +196,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." }
|
logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." }
|
||||||
val session = getConfirmedSessionIfPresent(otherParty, sessionFlow)
|
val session = getConfirmedSessionIfPresent(otherParty, sessionFlow)
|
||||||
val receivedSessionData: ReceivedSessionMessage<SessionData> = if (session == null) {
|
val receivedSessionData: ReceivedSessionMessage<SessionData> = if (session == null) {
|
||||||
val newSession = startNewSession(otherParty, sessionFlow, payload, waitForConfirmation = true, retryable = retrySend)
|
val newSession = initiateSession(otherParty, sessionFlow, payload, waitForConfirmation = true, retryable = retrySend)
|
||||||
// Only do a receive here as the session init has carried the payload
|
// Only do a receive here as the session init has carried the payload
|
||||||
receiveInternal(newSession, receiveType)
|
receiveInternal(newSession, receiveType)
|
||||||
} else {
|
} else {
|
||||||
@ -221,7 +231,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
val session = getConfirmedSessionIfPresent(otherParty, sessionFlow)
|
val session = getConfirmedSessionIfPresent(otherParty, sessionFlow)
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
// Don't send the payload again if it was already piggy-backed on a session init
|
// Don't send the payload again if it was already piggy-backed on a session init
|
||||||
startNewSession(otherParty, sessionFlow, payload, waitForConfirmation = false)
|
initiateSession(otherParty, sessionFlow, payload, waitForConfirmation = false)
|
||||||
} else {
|
} else {
|
||||||
sendInternal(session, createSessionData(session, payload))
|
sendInternal(session, createSessionData(session, payload))
|
||||||
}
|
}
|
||||||
@ -308,8 +318,8 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
private fun createSessionData(session: FlowSessionInternal, payload: Any): SessionData {
|
private fun createSessionData(session: FlowSessionInternal, payload: Any): SessionData {
|
||||||
val sessionState = session.state
|
val sessionState = session.state
|
||||||
val peerSessionId = when (sessionState) {
|
val peerSessionId = when (sessionState) {
|
||||||
is FlowSessionState.Initiating -> throw IllegalStateException("We've somehow held onto an unconfirmed session: $session")
|
|
||||||
is FlowSessionState.Initiated -> sessionState.peerSessionId
|
is FlowSessionState.Initiated -> sessionState.peerSessionId
|
||||||
|
else -> throw IllegalStateException("We've somehow held onto a non-initiated session: $session")
|
||||||
}
|
}
|
||||||
return SessionData(peerSessionId, payload)
|
return SessionData(peerSessionId, payload)
|
||||||
}
|
}
|
||||||
@ -332,37 +342,53 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun getConfirmedSessionIfPresent(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSessionInternal? {
|
private fun getConfirmedSessionIfPresent(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSessionInternal? {
|
||||||
return openSessions[Pair(sessionFlow, otherParty)]?.apply {
|
val session = openSessions[Pair(sessionFlow, otherParty)] ?: return null
|
||||||
if (state is FlowSessionState.Initiating) {
|
return when (session.state) {
|
||||||
// Session still initiating, wait for the confirmation
|
is FlowSessionState.Uninitiated -> null
|
||||||
waitForConfirmation()
|
is FlowSessionState.Initiating -> {
|
||||||
|
session.waitForConfirmation()
|
||||||
|
session
|
||||||
}
|
}
|
||||||
|
is FlowSessionState.Initiated -> session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun getConfirmedSession(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSessionInternal {
|
private fun getConfirmedSession(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSessionInternal {
|
||||||
return getConfirmedSessionIfPresent(otherParty, sessionFlow) ?:
|
return getConfirmedSessionIfPresent(otherParty, sessionFlow) ?:
|
||||||
startNewSession(otherParty, sessionFlow, null, waitForConfirmation = true)
|
initiateSession(otherParty, sessionFlow, null, waitForConfirmation = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun createNewSession(
|
||||||
* Creates a new session. The provided [otherParty] can be an identity of any advertised service on the network,
|
otherParty: Party,
|
||||||
* and might be advertised by more than one node. Therefore we first choose a single node that advertises it
|
sessionFlow: FlowLogic<*>
|
||||||
* and use its *legal identity* for communication. At the moment a single node can compose its legal identity out of
|
) {
|
||||||
* multiple public keys, but we **don't support multiple nodes advertising the same legal identity**.
|
logger.trace { "Creating a new session with $otherParty" }
|
||||||
*/
|
val session = FlowSessionInternal(sessionFlow, random63BitValue(), null, FlowSessionState.Uninitiated(otherParty))
|
||||||
@Suspendable
|
|
||||||
private fun startNewSession(otherParty: Party,
|
|
||||||
sessionFlow: FlowLogic<*>,
|
|
||||||
firstPayload: Any?,
|
|
||||||
waitForConfirmation: Boolean,
|
|
||||||
retryable: Boolean = false): FlowSessionInternal {
|
|
||||||
logger.trace { "Initiating a new session with $otherParty" }
|
|
||||||
val session = FlowSessionInternal(sessionFlow, random63BitValue(), null, FlowSessionState.Initiating(otherParty), retryable)
|
|
||||||
openSessions[Pair(sessionFlow, otherParty)] = session
|
openSessions[Pair(sessionFlow, otherParty)] = session
|
||||||
val (version, initiatingFlowClass) = sessionFlow.javaClass.flowVersionAndInitiatingClass
|
}
|
||||||
val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, sessionFlow.javaClass.appName, firstPayload)
|
|
||||||
|
@Suspendable
|
||||||
|
private fun initiateSession(
|
||||||
|
otherParty: Party,
|
||||||
|
sessionFlow: FlowLogic<*>,
|
||||||
|
firstPayload: Any?,
|
||||||
|
waitForConfirmation: Boolean,
|
||||||
|
retryable: Boolean = false
|
||||||
|
): FlowSessionInternal {
|
||||||
|
val session = openSessions[Pair(sessionFlow, otherParty)]
|
||||||
|
if (session == null) {
|
||||||
|
throw IllegalStateException("Expected an Uninitiated session for $otherParty")
|
||||||
|
}
|
||||||
|
val state = session.state
|
||||||
|
if (state !is FlowSessionState.Uninitiated) {
|
||||||
|
throw IllegalStateException("Tried to initiate a session $session, but it's already initiating/initiated")
|
||||||
|
}
|
||||||
|
logger.trace { "Initiating a new session with ${state.otherParty}" }
|
||||||
|
session.state = FlowSessionState.Initiating(state.otherParty)
|
||||||
|
session.retryable = retryable
|
||||||
|
val (version, initiatingFlowClass) = session.flow.javaClass.flowVersionAndInitiatingClass
|
||||||
|
val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, session.flow.javaClass.appName, firstPayload)
|
||||||
sendInternal(session, sessionInit)
|
sendInternal(session, sessionInit)
|
||||||
if (waitForConfirmation) {
|
if (waitForConfirmation) {
|
||||||
session.waitForConfirmation()
|
session.waitForConfirmation()
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.contracts.StateRef
|
|||||||
import net.corda.core.crypto.DigitalSignature
|
import net.corda.core.crypto.DigitalSignature
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.NotaryError
|
import net.corda.core.flows.NotaryError
|
||||||
import net.corda.core.flows.NotaryException
|
import net.corda.core.flows.NotaryException
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
@ -71,19 +72,19 @@ class BFTNonValidatingNotaryService(override val services: ServiceHubInternal,
|
|||||||
|
|
||||||
fun commitTransaction(tx: Any, otherSide: Party) = client.commitTransaction(tx, otherSide)
|
fun commitTransaction(tx: Any, otherSide: Party) = client.commitTransaction(tx, otherSide)
|
||||||
|
|
||||||
override fun createServiceFlow(otherParty: Party): FlowLogic<Void?> = ServiceFlow(otherParty, this)
|
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = ServiceFlow(otherPartySession, this)
|
||||||
|
|
||||||
private class ServiceFlow(val otherSide: Party, val service: BFTNonValidatingNotaryService) : FlowLogic<Void?>() {
|
private class ServiceFlow(val otherSideSession: FlowSession, val service: BFTNonValidatingNotaryService) : FlowLogic<Void?>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Void? {
|
override fun call(): Void? {
|
||||||
val stx = receive<FilteredTransaction>(otherSide).unwrap { it }
|
val stx = otherSideSession.receive<FilteredTransaction>().unwrap { it }
|
||||||
val signatures = commit(stx)
|
val signatures = commit(stx)
|
||||||
send(otherSide, signatures)
|
otherSideSession.send(signatures)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun commit(stx: FilteredTransaction): List<DigitalSignature> {
|
private fun commit(stx: FilteredTransaction): List<DigitalSignature> {
|
||||||
val response = service.commitTransaction(stx, otherSide)
|
val response = service.commitTransaction(stx, otherSideSession.counterparty)
|
||||||
when (response) {
|
when (response) {
|
||||||
is BFTSMaRt.ClusterResponse.Error -> throw NotaryException(response.error)
|
is BFTSMaRt.ClusterResponse.Error -> throw NotaryException(response.error)
|
||||||
is BFTSMaRt.ClusterResponse.Signatures -> {
|
is BFTSMaRt.ClusterResponse.Signatures -> {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.contracts.ComponentGroupEnum
|
import net.corda.core.contracts.ComponentGroupEnum
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.flows.NotaryFlow
|
||||||
import net.corda.core.flows.TransactionParts
|
import net.corda.core.flows.TransactionParts
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
|
|
||||||
class NonValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSide, service) {
|
class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSideSession, service) {
|
||||||
/**
|
/**
|
||||||
* The received transaction is not checked for contract-validity, as that would require fully
|
* The received transaction is not checked for contract-validity, as that would require fully
|
||||||
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
|
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
|
||||||
@ -21,7 +21,7 @@ class NonValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryS
|
|||||||
*/
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun receiveAndVerifyTx(): TransactionParts {
|
override fun receiveAndVerifyTx(): TransactionParts {
|
||||||
val parts = receive<Any>(otherSide).unwrap {
|
val parts = otherSideSession.receive<Any>().unwrap {
|
||||||
when (it) {
|
when (it) {
|
||||||
is FilteredTransaction -> {
|
is FilteredTransaction -> {
|
||||||
it.verify()
|
it.verify()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.flows.NotaryFlow
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.services.TimeWindowChecker
|
import net.corda.core.node.services.TimeWindowChecker
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
@ -16,7 +16,7 @@ class RaftNonValidatingNotaryService(override val services: ServiceHubInternal,
|
|||||||
override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock)
|
override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock)
|
||||||
override val uniquenessProvider: RaftUniquenessProvider = RaftUniquenessProvider(services)
|
override val uniquenessProvider: RaftUniquenessProvider = RaftUniquenessProvider(services)
|
||||||
|
|
||||||
override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = NonValidatingNotaryFlow(otherParty, this)
|
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this)
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
uniquenessProvider.start()
|
uniquenessProvider.start()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.flows.NotaryFlow
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.services.TimeWindowChecker
|
import net.corda.core.node.services.TimeWindowChecker
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
@ -16,7 +16,7 @@ class RaftValidatingNotaryService(override val services: ServiceHubInternal, ove
|
|||||||
override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock)
|
override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock)
|
||||||
override val uniquenessProvider: RaftUniquenessProvider = RaftUniquenessProvider(services)
|
override val uniquenessProvider: RaftUniquenessProvider = RaftUniquenessProvider(services)
|
||||||
|
|
||||||
override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = ValidatingNotaryFlow(otherParty, this)
|
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this)
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
uniquenessProvider.start()
|
uniquenessProvider.start()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.flows.NotaryFlow
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.services.TimeWindowChecker
|
import net.corda.core.node.services.TimeWindowChecker
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.nodeapi.ServiceType
|
import net.corda.nodeapi.ServiceType
|
||||||
@ -17,7 +17,7 @@ class SimpleNotaryService(override val services: ServiceHubInternal, override va
|
|||||||
override val timeWindowChecker = TimeWindowChecker(services.clock)
|
override val timeWindowChecker = TimeWindowChecker(services.clock)
|
||||||
override val uniquenessProvider = PersistentUniquenessProvider()
|
override val uniquenessProvider = PersistentUniquenessProvider()
|
||||||
|
|
||||||
override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = NonValidatingNotaryFlow(otherParty, this)
|
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this)
|
||||||
|
|
||||||
override fun start() {}
|
override fun start() {}
|
||||||
override fun stop() {}
|
override fun stop() {}
|
||||||
|
@ -3,7 +3,6 @@ package net.corda.node.services.transactions
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.TransactionVerificationException
|
import net.corda.core.contracts.TransactionVerificationException
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
@ -14,7 +13,7 @@ import java.security.SignatureException
|
|||||||
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
|
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
|
||||||
* indeed valid.
|
* indeed valid.
|
||||||
*/
|
*/
|
||||||
class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSide, service) {
|
class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSideSession, service) {
|
||||||
/**
|
/**
|
||||||
* The received transaction is checked for contract-validity, which requires fully resolving it into a
|
* The received transaction is checked for contract-validity, which requires fully resolving it into a
|
||||||
* [TransactionForVerification], for which the caller also has to to reveal the whole transaction
|
* [TransactionForVerification], for which the caller also has to to reveal the whole transaction
|
||||||
@ -23,7 +22,7 @@ class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryServ
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun receiveAndVerifyTx(): TransactionParts {
|
override fun receiveAndVerifyTx(): TransactionParts {
|
||||||
try {
|
try {
|
||||||
val stx = subFlow(ReceiveTransactionFlow(otherSide, checkSufficientSignatures = false))
|
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
|
||||||
val notary = stx.notary
|
val notary = stx.notary
|
||||||
checkNotary(notary)
|
checkNotary(notary)
|
||||||
checkSignatures(stx)
|
checkSignatures(stx)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.flows.NotaryFlow
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.services.TimeWindowChecker
|
import net.corda.core.node.services.TimeWindowChecker
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.nodeapi.ServiceType
|
import net.corda.nodeapi.ServiceType
|
||||||
@ -17,7 +17,7 @@ class ValidatingNotaryService(override val services: ServiceHubInternal, overrid
|
|||||||
override val timeWindowChecker = TimeWindowChecker(services.clock)
|
override val timeWindowChecker = TimeWindowChecker(services.clock)
|
||||||
override val uniquenessProvider = PersistentUniquenessProvider()
|
override val uniquenessProvider = PersistentUniquenessProvider()
|
||||||
|
|
||||||
override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = ValidatingNotaryFlow(otherParty, this)
|
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this)
|
||||||
|
|
||||||
override fun start() {}
|
override fun start() {}
|
||||||
override fun stop() {}
|
override fun stop() {}
|
||||||
|
@ -4,7 +4,10 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.copyToDirectory
|
||||||
|
import net.corda.core.internal.createDirectories
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.internal.list
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -68,21 +71,22 @@ class CordappSmokeTest {
|
|||||||
override fun call(): Pair<FlowInfo, FlowInfo> {
|
override fun call(): Pair<FlowInfo, FlowInfo> {
|
||||||
// This receive will kick off SendBackInitiatorFlowContext by sending a session-init with our app name.
|
// This receive will kick off SendBackInitiatorFlowContext by sending a session-init with our app name.
|
||||||
// SendBackInitiatorFlowContext will send back our context using the information from this session-init
|
// SendBackInitiatorFlowContext will send back our context using the information from this session-init
|
||||||
val sessionInitContext = receive<FlowInfo>(otherParty).unwrap { it }
|
val session = initiateFlow(otherParty)
|
||||||
|
val sessionInitContext = session.receive<FlowInfo>().unwrap { it }
|
||||||
// This context is taken from the session-confirm message
|
// This context is taken from the session-confirm message
|
||||||
val sessionConfirmContext = getFlowInfo(otherParty)
|
val sessionConfirmContext = session.getCounterpartyFlowInfo()
|
||||||
return Pair(sessionInitContext, sessionConfirmContext)
|
return Pair(sessionInitContext, sessionConfirmContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@InitiatedBy(GatherContextsFlow::class)
|
@InitiatedBy(GatherContextsFlow::class)
|
||||||
class SendBackInitiatorFlowContext(private val otherParty: Party) : FlowLogic<Unit>() {
|
class SendBackInitiatorFlowContext(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
// An initiated flow calling getFlowContext on its initiator will get the context from the session-init
|
// An initiated flow calling getFlowContext on its initiator will get the context from the session-init
|
||||||
val sessionInitContext = getFlowInfo(otherParty)
|
val sessionInitContext = otherPartySession.getCounterpartyFlowInfo()
|
||||||
send(otherParty, sessionInitContext)
|
otherPartySession.send(sessionInitContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
package net.corda.node.cordapp
|
package net.corda.node.cordapp
|
||||||
|
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.node.internal.cordapp.Cordapp
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import org.junit.Assert
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
|
|
||||||
|
@InitiatingFlow
|
||||||
class DummyFlow : FlowLogic<Unit>() {
|
class DummyFlow : FlowLogic<Unit>() {
|
||||||
override fun call() { }
|
override fun call() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(DummyFlow::class)
|
@InitiatedBy(DummyFlow::class)
|
||||||
class LoaderTestFlow : FlowLogic<Unit>() {
|
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
override fun call() { }
|
override fun call() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.StateMachineRunId
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
@ -562,9 +559,10 @@ class TwoPartyTradeFlowTests {
|
|||||||
} else {
|
} else {
|
||||||
ourIdentityAndCert
|
ourIdentityAndCert
|
||||||
}
|
}
|
||||||
send(buyer, TestTx(notary, price, anonymous))
|
val buyerSession = initiateFlow(buyer)
|
||||||
|
buyerSession.send(TestTx(notary, price, anonymous))
|
||||||
return subFlow(Seller(
|
return subFlow(Seller(
|
||||||
buyer,
|
buyerSession,
|
||||||
assetToSell,
|
assetToSell,
|
||||||
price,
|
price,
|
||||||
myPartyAndCert))
|
myPartyAndCert))
|
||||||
@ -572,14 +570,14 @@ class TwoPartyTradeFlowTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(SellerInitiator::class)
|
@InitiatedBy(SellerInitiator::class)
|
||||||
class BuyerAcceptor(private val seller: Party) : FlowLogic<SignedTransaction>() {
|
class BuyerAcceptor(private val sellerSession: FlowSession) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
val (notary, price, anonymous) = receive<TestTx>(seller).unwrap {
|
val (notary, price, anonymous) = sellerSession.receive<TestTx>().unwrap {
|
||||||
require(serviceHub.networkMapCache.isNotary(it.notaryIdentity)) { "${it.notaryIdentity} is not a notary" }
|
require(serviceHub.networkMapCache.isNotary(it.notaryIdentity)) { "${it.notaryIdentity} is not a notary" }
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
return subFlow(Buyer(seller, notary, price, CommercialPaper.State::class.java, anonymous))
|
return subFlow(Buyer(sellerSession, notary, price, CommercialPaper.State::class.java, anonymous))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.AbstractParty
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.VaultQueryService
|
import net.corda.core.node.services.VaultQueryService
|
||||||
import net.corda.core.node.services.queryBy
|
import net.corda.core.node.services.queryBy
|
||||||
@ -16,10 +15,10 @@ import net.corda.core.node.services.vault.SortAttribute
|
|||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.nodeapi.ServiceInfo
|
|
||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||||
|
import net.corda.nodeapi.ServiceInfo
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
|
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
|
||||||
@ -50,15 +49,15 @@ class ScheduledFlowTests {
|
|||||||
val processed: Boolean = false,
|
val processed: Boolean = false,
|
||||||
override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState {
|
override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState {
|
||||||
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
||||||
if (!processed) {
|
return if (!processed) {
|
||||||
val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.java, thisStateRef)
|
val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.java, thisStateRef)
|
||||||
return ScheduledActivity(logicRef, creationTime)
|
ScheduledActivity(logicRef, creationTime)
|
||||||
} else {
|
} else {
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val participants: List<AbstractParty> = listOf(source, destination)
|
override val participants: List<Party> get() = listOf(source, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
class InsertInitialStateFlow(private val destination: Party) : FlowLogic<Unit>() {
|
class InsertInitialStateFlow(private val destination: Party) : FlowLogic<Unit>() {
|
||||||
@ -70,7 +69,7 @@ class ScheduledFlowTests {
|
|||||||
.addOutputState(scheduledState, DUMMY_PROGRAM_ID)
|
.addOutputState(scheduledState, DUMMY_PROGRAM_ID)
|
||||||
.addCommand(dummyCommand(ourIdentity.owningKey))
|
.addCommand(dummyCommand(ourIdentity.owningKey))
|
||||||
val tx = serviceHub.signInitialTransaction(builder)
|
val tx = serviceHub.signInitialTransaction(builder)
|
||||||
subFlow(FinalityFlow(tx, setOf(ourIdentity)))
|
subFlow(FinalityFlow(tx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ class ScheduledFlowTests {
|
|||||||
.addOutputState(newStateOutput, DUMMY_PROGRAM_ID)
|
.addOutputState(newStateOutput, DUMMY_PROGRAM_ID)
|
||||||
.addCommand(dummyCommand(ourIdentity.owningKey))
|
.addCommand(dummyCommand(ourIdentity.owningKey))
|
||||||
val tx = serviceHub.signInitialTransaction(builder)
|
val tx = serviceHub.signInitialTransaction(builder)
|
||||||
subFlow(FinalityFlow(tx, setOf(scheduledState.source, scheduledState.destination)))
|
subFlow(FinalityFlow(tx, setOf(scheduledState.destination)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.node.services.network
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
@ -170,17 +171,18 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
|
|||||||
override fun call(): String {
|
override fun call(): String {
|
||||||
println("SEND FLOW to $otherParty")
|
println("SEND FLOW to $otherParty")
|
||||||
println("Party key ${otherParty.owningKey.toBase58String()}")
|
println("Party key ${otherParty.owningKey.toBase58String()}")
|
||||||
return sendAndReceive<String>(otherParty, "Hi!").unwrap { it }
|
val session = initiateFlow(otherParty)
|
||||||
|
return session.sendAndReceive<String>("Hi!").unwrap { it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(SendFlow::class)
|
@InitiatedBy(SendFlow::class)
|
||||||
private class SendBackFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
private class SendBackFlow(val otherSideSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
println("SEND BACK FLOW to $otherParty")
|
println("SEND BACK FLOW to ${otherSideSession.counterparty}")
|
||||||
println("Party key ${otherParty.owningKey.toBase58String()}")
|
println("Party key ${otherSideSession.counterparty.owningKey.toBase58String()}")
|
||||||
send(otherParty, "Hello!")
|
otherSideSession.send("Hello!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
package net.corda.node.services.persistence
|
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
|
||||||
import net.corda.core.contracts.Amount
|
|
||||||
import net.corda.core.contracts.Issued
|
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.SendTransactionFlow
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.services.queryBy
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import net.corda.finance.USD
|
|
||||||
import net.corda.finance.contracts.asset.Cash
|
|
||||||
import net.corda.node.internal.StartedNode
|
|
||||||
import net.corda.node.services.NotifyTransactionHandler
|
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
|
||||||
import net.corda.testing.MEGA_CORP
|
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.node.MockNetwork
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the data vending service.
|
|
||||||
*/
|
|
||||||
class DataVendingServiceTests {
|
|
||||||
lateinit var mockNet: MockNetwork
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
mockNet = MockNetwork()
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun cleanUp() {
|
|
||||||
mockNet.stopNodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `notify of transaction`() {
|
|
||||||
val nodes = mockNet.createSomeNodes(2)
|
|
||||||
val vaultServiceNode = nodes.partyNodes[0]
|
|
||||||
val registerNode = nodes.partyNodes[1]
|
|
||||||
val beneficiary = vaultServiceNode.info.chooseIdentity()
|
|
||||||
val deposit = registerNode.info.chooseIdentity().ref(1)
|
|
||||||
mockNet.runNetwork()
|
|
||||||
|
|
||||||
// Generate an issuance transaction
|
|
||||||
val ptx = TransactionBuilder(null)
|
|
||||||
Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY)
|
|
||||||
|
|
||||||
// Complete the cash transaction, and then manually relay it
|
|
||||||
val tx = registerNode.services.signInitialTransaction(ptx)
|
|
||||||
vaultServiceNode.database.transaction {
|
|
||||||
assertThat(vaultServiceNode.services.vaultQueryService.queryBy<Cash.State>().states.isEmpty())
|
|
||||||
|
|
||||||
registerNode.sendNotifyTx(tx, vaultServiceNode)
|
|
||||||
|
|
||||||
// Check the transaction is in the receiving node
|
|
||||||
val actual = vaultServiceNode.services.vaultQueryService.queryBy<Cash.State>().states.singleOrNull()
|
|
||||||
val expected = tx.tx.outRef<Cash.State>(0)
|
|
||||||
assertEquals(expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that invalid transactions are rejected.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
fun `notify failure`() {
|
|
||||||
val nodes = mockNet.createSomeNodes(2)
|
|
||||||
val vaultServiceNode = nodes.partyNodes[0]
|
|
||||||
val registerNode = nodes.partyNodes[1]
|
|
||||||
val beneficiary = vaultServiceNode.info.chooseIdentity()
|
|
||||||
val deposit = MEGA_CORP.ref(1)
|
|
||||||
mockNet.runNetwork()
|
|
||||||
|
|
||||||
// Generate an issuance transaction
|
|
||||||
val ptx = TransactionBuilder(DUMMY_NOTARY)
|
|
||||||
Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY)
|
|
||||||
|
|
||||||
// The transaction tries issuing MEGA_CORP cash, but we aren't the issuer, so it's invalid
|
|
||||||
val tx = registerNode.services.signInitialTransaction(ptx)
|
|
||||||
vaultServiceNode.database.transaction {
|
|
||||||
assertThat(vaultServiceNode.services.vaultQueryService.queryBy<Cash.State>().states.isEmpty())
|
|
||||||
|
|
||||||
registerNode.sendNotifyTx(tx, vaultServiceNode)
|
|
||||||
|
|
||||||
// Check the transaction is not in the receiving node
|
|
||||||
assertThat(vaultServiceNode.services.vaultQueryService.queryBy<Cash.State>().states.isEmpty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun StartedNode<*>.sendNotifyTx(tx: SignedTransaction, walletServiceNode: StartedNode<*>) {
|
|
||||||
walletServiceNode.internals.registerInitiatedFlow(InitiateNotifyTxFlow::class.java)
|
|
||||||
services.startFlow(NotifyTxFlow(walletServiceNode.info.chooseIdentity(), tx))
|
|
||||||
mockNet.runNetwork()
|
|
||||||
}
|
|
||||||
|
|
||||||
@InitiatingFlow
|
|
||||||
private class NotifyTxFlow(val otherParty: Party, val stx: SignedTransaction) : FlowLogic<Void?>() {
|
|
||||||
@Suspendable
|
|
||||||
override fun call() = subFlow(SendTransactionFlow(otherParty, stx))
|
|
||||||
}
|
|
||||||
|
|
||||||
@InitiatedBy(NotifyTxFlow::class)
|
|
||||||
private class InitiateNotifyTxFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
|
||||||
@Suspendable
|
|
||||||
override fun call() = subFlow(NotifyTransactionHandler(otherParty))
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,7 +37,7 @@ class NodeSchemaServiceTest {
|
|||||||
/**
|
/**
|
||||||
* Note: this test verifies auto-scanning to register identified [MappedSchema] schemas.
|
* Note: this test verifies auto-scanning to register identified [MappedSchema] schemas.
|
||||||
* By default, Driver uses the caller package for auto-scanning:
|
* By default, Driver uses the caller package for auto-scanning:
|
||||||
* System.setProperty("net.corda.node.cordapp.scan.package", callerPackage)
|
* System.setProperty("net.corda.node.cordapp.scan.packages", callerPackage)
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun `auto scanning of custom schemas for testing with Driver`() {
|
fun `auto scanning of custom schemas for testing with Driver`() {
|
||||||
|
@ -124,7 +124,7 @@ class FlowFrameworkTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `exception while fiber suspended`() {
|
fun `exception while fiber suspended`() {
|
||||||
node2.registerFlowFactory(ReceiveFlow::class) { SendFlow("Hello", it) }
|
node2.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) }
|
||||||
val flow = ReceiveFlow(node2.info.chooseIdentity())
|
val flow = ReceiveFlow(node2.info.chooseIdentity())
|
||||||
val fiber = node1.services.startFlow(flow) as FlowStateMachineImpl
|
val fiber = node1.services.startFlow(flow) as FlowStateMachineImpl
|
||||||
// Before the flow runs change the suspend action to throw an exception
|
// Before the flow runs change the suspend action to throw an exception
|
||||||
@ -143,7 +143,7 @@ class FlowFrameworkTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `flow restarted just after receiving payload`() {
|
fun `flow restarted just after receiving payload`() {
|
||||||
node2.registerFlowFactory(SendFlow::class) { ReceiveFlow(it).nonTerminating() }
|
node2.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() }
|
||||||
node1.services.startFlow(SendFlow("Hello", node2.info.chooseIdentity()))
|
node1.services.startFlow(SendFlow("Hello", node2.info.chooseIdentity()))
|
||||||
|
|
||||||
// We push through just enough messages to get only the payload sent
|
// We push through just enough messages to get only the payload sent
|
||||||
@ -152,7 +152,7 @@ class FlowFrameworkTests {
|
|||||||
node2.internals.acceptableLiveFiberCountOnStop = 1
|
node2.internals.acceptableLiveFiberCountOnStop = 1
|
||||||
node2.dispose()
|
node2.dispose()
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val restoredFlow = node2.restartAndGetRestoredFlow<ReceiveFlow>(node1)
|
val restoredFlow = node2.restartAndGetRestoredFlow<InitiatedReceiveFlow>(node1)
|
||||||
assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello")
|
assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ class FlowFrameworkTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `flow loaded from checkpoint will respond to messages from before start`() {
|
fun `flow loaded from checkpoint will respond to messages from before start`() {
|
||||||
node1.registerFlowFactory(ReceiveFlow::class) { SendFlow("Hello", it) }
|
node1.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) }
|
||||||
node2.services.startFlow(ReceiveFlow(node1.info.chooseIdentity()).nonTerminating()) // Prepare checkpointed receive flow
|
node2.services.startFlow(ReceiveFlow(node1.info.chooseIdentity()).nonTerminating()) // Prepare checkpointed receive flow
|
||||||
// Make sure the add() has finished initial processing.
|
// Make sure the add() has finished initial processing.
|
||||||
node2.smm.executor.flush()
|
node2.smm.executor.flush()
|
||||||
@ -260,13 +260,13 @@ class FlowFrameworkTests {
|
|||||||
fun `sending to multiple parties`() {
|
fun `sending to multiple parties`() {
|
||||||
val node3 = mockNet.createNode(node1.network.myAddress)
|
val node3 = mockNet.createNode(node1.network.myAddress)
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
node2.registerFlowFactory(SendFlow::class) { ReceiveFlow(it).nonTerminating() }
|
node2.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() }
|
||||||
node3.registerFlowFactory(SendFlow::class) { ReceiveFlow(it).nonTerminating() }
|
node3.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() }
|
||||||
val payload = "Hello World"
|
val payload = "Hello World"
|
||||||
node1.services.startFlow(SendFlow(payload, node2.info.chooseIdentity(), node3.info.chooseIdentity()))
|
node1.services.startFlow(SendFlow(payload, node2.info.chooseIdentity(), node3.info.chooseIdentity()))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val node2Flow = node2.getSingleFlow<ReceiveFlow>().first
|
val node2Flow = node2.getSingleFlow<InitiatedReceiveFlow>().first
|
||||||
val node3Flow = node3.getSingleFlow<ReceiveFlow>().first
|
val node3Flow = node3.getSingleFlow<InitiatedReceiveFlow>().first
|
||||||
assertThat(node2Flow.receivedPayloads[0]).isEqualTo(payload)
|
assertThat(node2Flow.receivedPayloads[0]).isEqualTo(payload)
|
||||||
assertThat(node3Flow.receivedPayloads[0]).isEqualTo(payload)
|
assertThat(node3Flow.receivedPayloads[0]).isEqualTo(payload)
|
||||||
|
|
||||||
@ -294,8 +294,8 @@ class FlowFrameworkTests {
|
|||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val node2Payload = "Test 1"
|
val node2Payload = "Test 1"
|
||||||
val node3Payload = "Test 2"
|
val node3Payload = "Test 2"
|
||||||
node2.registerFlowFactory(ReceiveFlow::class) { SendFlow(node2Payload, it) }
|
node2.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow(node2Payload, it) }
|
||||||
node3.registerFlowFactory(ReceiveFlow::class) { SendFlow(node3Payload, it) }
|
node3.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow(node3Payload, it) }
|
||||||
val multiReceiveFlow = ReceiveFlow(node2.info.chooseIdentity(), node3.info.chooseIdentity()).nonTerminating()
|
val multiReceiveFlow = ReceiveFlow(node2.info.chooseIdentity(), node3.info.chooseIdentity()).nonTerminating()
|
||||||
node1.services.startFlow(multiReceiveFlow)
|
node1.services.startFlow(multiReceiveFlow)
|
||||||
node1.internals.acceptableLiveFiberCountOnStop = 1
|
node1.internals.acceptableLiveFiberCountOnStop = 1
|
||||||
@ -420,10 +420,11 @@ class FlowFrameworkTests {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
// Kick off the flow on the other side ...
|
// Kick off the flow on the other side ...
|
||||||
send(otherParty, 1)
|
val session = initiateFlow(otherParty)
|
||||||
|
session.send(1)
|
||||||
// ... then pause this one until it's received the session-end message from the other side
|
// ... then pause this one until it's received the session-end message from the other side
|
||||||
receivedOtherFlowEnd.acquire()
|
receivedOtherFlowEnd.acquire()
|
||||||
sendAndReceive<Int>(otherParty, 2)
|
session.sendAndReceive<Int>(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,14 +544,14 @@ class FlowFrameworkTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConditionalExceptionFlow(val otherParty: Party, val sendPayload: Any) : FlowLogic<Unit>() {
|
private class ConditionalExceptionFlow(val otherPartySession: FlowSession, val sendPayload: Any) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val throwException = receive<Boolean>(otherParty).unwrap { it }
|
val throwException = otherPartySession.receive<Boolean>().unwrap { it }
|
||||||
if (throwException) {
|
if (throwException) {
|
||||||
throw MyFlowException("Throwing exception as requested")
|
throw MyFlowException("Throwing exception as requested")
|
||||||
}
|
}
|
||||||
send(otherParty, sendPayload)
|
otherPartySession.send(sendPayload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +560,7 @@ class FlowFrameworkTests {
|
|||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class AskForExceptionFlow(val otherParty: Party, val throwException: Boolean) : FlowLogic<String>() {
|
class AskForExceptionFlow(val otherParty: Party, val throwException: Boolean) : FlowLogic<String>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): String = sendAndReceive<String>(otherParty, throwException).unwrap { it }
|
override fun call(): String = initiateFlow(otherParty).sendAndReceive<String>(throwException).unwrap { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
class RetryOnExceptionFlow(val otherParty: Party) : FlowLogic<String>() {
|
class RetryOnExceptionFlow(val otherParty: Party) : FlowLogic<String>() {
|
||||||
@ -581,7 +582,7 @@ class FlowFrameworkTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `serialisation issue in counterparty`() {
|
fun `serialisation issue in counterparty`() {
|
||||||
node2.registerFlowFactory(ReceiveFlow::class) { SendFlow(NonSerialisableData(1), it) }
|
node2.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow(NonSerialisableData(1), it) }
|
||||||
val result = node1.services.startFlow(ReceiveFlow(node2.info.chooseIdentity())).resultFuture
|
val result = node1.services.startFlow(ReceiveFlow(node2.info.chooseIdentity())).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy {
|
assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy {
|
||||||
@ -651,7 +652,7 @@ class FlowFrameworkTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `customised client flow`() {
|
fun `customised client flow`() {
|
||||||
val receiveFlowFuture = node2.registerFlowFactory(SendFlow::class) { ReceiveFlow(it) }
|
val receiveFlowFuture = node2.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it) }
|
||||||
node1.services.startFlow(CustomSendFlow("Hello", node2.info.chooseIdentity())).resultFuture
|
node1.services.startFlow(CustomSendFlow("Hello", node2.info.chooseIdentity())).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertThat(receiveFlowFuture.getOrThrow().receivedPayloads).containsOnly("Hello")
|
assertThat(receiveFlowFuture.getOrThrow().receivedPayloads).containsOnly("Hello")
|
||||||
@ -668,7 +669,7 @@ class FlowFrameworkTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `upgraded initiating flow`() {
|
fun `upgraded initiating flow`() {
|
||||||
node2.registerFlowFactory(UpgradedFlow::class, initiatedFlowVersion = 1) { SendFlow("Old initiated", it) }
|
node2.registerFlowFactory(UpgradedFlow::class, initiatedFlowVersion = 1) { InitiatedSendFlow("Old initiated", it) }
|
||||||
val result = node1.services.startFlow(UpgradedFlow(node2.info.chooseIdentity())).resultFuture
|
val result = node1.services.startFlow(UpgradedFlow(node2.info.chooseIdentity())).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertThat(receivedSessionMessages).startsWith(
|
assertThat(receivedSessionMessages).startsWith(
|
||||||
@ -684,13 +685,13 @@ class FlowFrameworkTests {
|
|||||||
fun `upgraded initiated flow`() {
|
fun `upgraded initiated flow`() {
|
||||||
node2.registerFlowFactory(SendFlow::class, initiatedFlowVersion = 2) { UpgradedFlow(it) }
|
node2.registerFlowFactory(SendFlow::class, initiatedFlowVersion = 2) { UpgradedFlow(it) }
|
||||||
val initiatingFlow = SendFlow("Old initiating", node2.info.chooseIdentity())
|
val initiatingFlow = SendFlow("Old initiating", node2.info.chooseIdentity())
|
||||||
node1.services.startFlow(initiatingFlow)
|
val flowInfo = node1.services.startFlow(initiatingFlow).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertThat(receivedSessionMessages).startsWith(
|
assertThat(receivedSessionMessages).startsWith(
|
||||||
node1 sent sessionInit(SendFlow::class, flowVersion = 1, payload = "Old initiating") to node2,
|
node1 sent sessionInit(SendFlow::class, flowVersion = 1, payload = "Old initiating") to node2,
|
||||||
node2 sent sessionConfirm(flowVersion = 2) to node1
|
node2 sent sessionConfirm(flowVersion = 2) to node1
|
||||||
)
|
)
|
||||||
assertThat(initiatingFlow.getFlowInfo(node2.info.chooseIdentity()).flowVersion).isEqualTo(2)
|
assertThat(flowInfo.get().flowVersion).isEqualTo(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -736,6 +737,23 @@ class FlowFrameworkTests {
|
|||||||
assertThat(result.getOrThrow()).isEqualTo("HelloHello")
|
assertThat(result.getOrThrow()).isEqualTo("HelloHello")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `double initiateFlow throws`() {
|
||||||
|
val future = node1.services.startFlow(DoubleInitiatingFlow()).resultFuture
|
||||||
|
mockNet.runNetwork()
|
||||||
|
assertThatExceptionOfType(IllegalStateException::class.java)
|
||||||
|
.isThrownBy { future.getOrThrow() }
|
||||||
|
.withMessageContaining("Attempted to initiateFlow() twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitiatingFlow
|
||||||
|
private class DoubleInitiatingFlow : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() {
|
||||||
|
initiateFlow(serviceHub.myInfo.chooseIdentity())
|
||||||
|
initiateFlow(serviceHub.myInfo.chooseIdentity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//region Helpers
|
//region Helpers
|
||||||
@ -754,16 +772,7 @@ class FlowFrameworkTests {
|
|||||||
return smm.findStateMachines(P::class.java).single()
|
return smm.findStateMachines(P::class.java).single()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Use registerFlowFactoryExpectingFlowSession() instead")
|
|
||||||
private inline fun <reified P : FlowLogic<*>> StartedNode<*>.registerFlowFactory(
|
private inline fun <reified P : FlowLogic<*>> StartedNode<*>.registerFlowFactory(
|
||||||
initiatingFlowClass: KClass<out FlowLogic<*>>,
|
|
||||||
initiatedFlowVersion: Int = 1,
|
|
||||||
noinline flowFactory: (Party) -> P): CordaFuture<P>
|
|
||||||
{
|
|
||||||
return registerFlowFactoryExpectingFlowSession(initiatingFlowClass, initiatedFlowVersion, { flowFactory(it.counterparty) })
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified P : FlowLogic<*>> StartedNode<*>.registerFlowFactoryExpectingFlowSession(
|
|
||||||
initiatingFlowClass: KClass<out FlowLogic<*>>,
|
initiatingFlowClass: KClass<out FlowLogic<*>>,
|
||||||
initiatedFlowVersion: Int = 1,
|
initiatedFlowVersion: Int = 1,
|
||||||
noinline flowFactory: (FlowSession) -> P): CordaFuture<P>
|
noinline flowFactory: (FlowSession) -> P): CordaFuture<P>
|
||||||
@ -858,13 +867,25 @@ class FlowFrameworkTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
private open class SendFlow(val payload: Any, vararg val otherParties: Party) : FlowLogic<Unit>() {
|
private open class SendFlow(val payload: Any, vararg val otherParties: Party) : FlowLogic<FlowInfo>() {
|
||||||
init {
|
init {
|
||||||
require(otherParties.isNotEmpty())
|
require(otherParties.isNotEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = otherParties.forEach { send(it, payload) }
|
override fun call(): FlowInfo {
|
||||||
|
val flowInfos = otherParties.map {
|
||||||
|
val session = initiateFlow(it)
|
||||||
|
session.send(payload)
|
||||||
|
session.getCounterpartyFlowInfo()
|
||||||
|
}.toList()
|
||||||
|
return flowInfos.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private open class InitiatedSendFlow(val payload: Any, val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() = otherPartySession.send(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface CustomInterface
|
private interface CustomInterface
|
||||||
@ -890,7 +911,7 @@ class FlowFrameworkTests {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
progressTracker.currentStep = START_STEP
|
progressTracker.currentStep = START_STEP
|
||||||
receivedPayloads = otherParties.map { receive<String>(it).unwrap { it } }
|
receivedPayloads = otherParties.map { initiateFlow(it).receive<String>().unwrap { it } }
|
||||||
progressTracker.currentStep = RECEIVED_STEP
|
progressTracker.currentStep = RECEIVED_STEP
|
||||||
if (nonTerminating) {
|
if (nonTerminating) {
|
||||||
Fiber.park()
|
Fiber.park()
|
||||||
@ -903,26 +924,54 @@ class FlowFrameworkTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatingFlow
|
private class InitiatedReceiveFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
private class SendAndReceiveFlow(val otherParty: Party, val payload: Any) : FlowLogic<Any>() {
|
object START_STEP : ProgressTracker.Step("Starting")
|
||||||
@Suspendable
|
object RECEIVED_STEP : ProgressTracker.Step("Received")
|
||||||
override fun call(): Any = sendAndReceive<Any>(otherParty, payload).unwrap { it }
|
|
||||||
}
|
override val progressTracker: ProgressTracker = ProgressTracker(START_STEP, RECEIVED_STEP)
|
||||||
|
private var nonTerminating: Boolean = false
|
||||||
|
@Transient
|
||||||
|
var receivedPayloads: List<String> = emptyList()
|
||||||
|
|
||||||
private class InlinedSendFlow(val payload: String, val otherParty: Party) : FlowLogic<Unit>() {
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = send(otherParty, payload)
|
override fun call() {
|
||||||
|
progressTracker.currentStep = START_STEP
|
||||||
|
receivedPayloads = listOf(otherPartySession.receive<String>().unwrap { it })
|
||||||
|
progressTracker.currentStep = RECEIVED_STEP
|
||||||
|
if (nonTerminating) {
|
||||||
|
Fiber.park()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nonTerminating(): InitiatedReceiveFlow {
|
||||||
|
nonTerminating = true
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
private class PingPongFlow(val otherParty: Party, val payload: Long) : FlowLogic<Unit>() {
|
private class SendAndReceiveFlow(val otherParty: Party, val payload: Any, val otherPartySession: FlowSession? = null) : FlowLogic<Any>() {
|
||||||
|
constructor(otherPartySession: FlowSession, payload: Any) : this(otherPartySession.counterparty, payload, otherPartySession)
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): Any = (otherPartySession ?: initiateFlow(otherParty)).sendAndReceive<Any>(payload).unwrap { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InlinedSendFlow(val payload: String, val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() = otherPartySession.send(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitiatingFlow
|
||||||
|
private class PingPongFlow(val otherParty: Party, val payload: Long, val otherPartySession: FlowSession? = null) : FlowLogic<Unit>() {
|
||||||
|
constructor(otherPartySession: FlowSession, payload: Long) : this(otherPartySession.counterparty, payload, otherPartySession)
|
||||||
@Transient var receivedPayload: Long? = null
|
@Transient var receivedPayload: Long? = null
|
||||||
@Transient var receivedPayload2: Long? = null
|
@Transient var receivedPayload2: Long? = null
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
receivedPayload = sendAndReceive<Long>(otherParty, payload).unwrap { it }
|
val session = otherPartySession ?: initiateFlow(otherParty)
|
||||||
receivedPayload2 = sendAndReceive<Long>(otherParty, payload + 1).unwrap { it }
|
receivedPayload = session.sendAndReceive<Long>(payload).unwrap { it }
|
||||||
|
receivedPayload2 = session.sendAndReceive<Long>(payload + 1).unwrap { it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -950,17 +999,18 @@ class FlowFrameworkTests {
|
|||||||
class Waiter(val stx: SignedTransaction, val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
class Waiter(val stx: SignedTransaction, val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
send(otherParty, stx)
|
val otherPartySession = initiateFlow(otherParty)
|
||||||
|
otherPartySession.send(stx)
|
||||||
return waitForLedgerCommit(stx.id)
|
return waitForLedgerCommit(stx.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Committer(val otherParty: Party, val throwException: (() -> Exception)? = null) : FlowLogic<SignedTransaction>() {
|
class Committer(val otherPartySession: FlowSession, val throwException: (() -> Exception)? = null) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
val stx = receive<SignedTransaction>(otherParty).unwrap { it }
|
val stx = otherPartySession.receive<SignedTransaction>().unwrap { it }
|
||||||
if (throwException != null) throw throwException.invoke()
|
if (throwException != null) throw throwException.invoke()
|
||||||
return subFlow(FinalityFlow(stx, setOf(otherParty))).single()
|
return subFlow(FinalityFlow(stx, setOf(otherPartySession.counterparty)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -969,7 +1019,8 @@ class FlowFrameworkTests {
|
|||||||
private class VaultQueryFlow(val stx: SignedTransaction, val otherParty: Party) : FlowLogic<List<StateAndRef<ContractState>>>() {
|
private class VaultQueryFlow(val stx: SignedTransaction, val otherParty: Party) : FlowLogic<List<StateAndRef<ContractState>>>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): List<StateAndRef<ContractState>> {
|
override fun call(): List<StateAndRef<ContractState>> {
|
||||||
send(otherParty, stx)
|
val otherPartySession = initiateFlow(otherParty)
|
||||||
|
otherPartySession.send(stx)
|
||||||
// hold onto reference here to force checkpoint of vaultQueryService and thus
|
// hold onto reference here to force checkpoint of vaultQueryService and thus
|
||||||
// prove it is registered as a tokenizableService in the node
|
// prove it is registered as a tokenizableService in the node
|
||||||
val vaultQuerySvc = serviceHub.vaultQueryService
|
val vaultQuerySvc = serviceHub.vaultQueryService
|
||||||
@ -979,27 +1030,29 @@ class FlowFrameworkTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InitiatingFlow(version = 2)
|
@InitiatingFlow(version = 2)
|
||||||
private class UpgradedFlow(val otherParty: Party) : FlowLogic<Pair<Any, Int>>() {
|
private class UpgradedFlow(val otherParty: Party, val otherPartySession: FlowSession? = null) : FlowLogic<Pair<Any, Int>>() {
|
||||||
|
constructor(otherPartySession: FlowSession) : this(otherPartySession.counterparty, otherPartySession)
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Pair<Any, Int> {
|
override fun call(): Pair<Any, Int> {
|
||||||
val received = receive<Any>(otherParty).unwrap { it }
|
val otherPartySession = this.otherPartySession ?: initiateFlow(otherParty)
|
||||||
val otherFlowVersion = getFlowInfo(otherParty).flowVersion
|
val received = otherPartySession.receive<Any>().unwrap { it }
|
||||||
|
val otherFlowVersion = otherPartySession.getCounterpartyFlowInfo().flowVersion
|
||||||
return Pair(received, otherFlowVersion)
|
return Pair(received, otherFlowVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SingleInlinedSubFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
private class SingleInlinedSubFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val payload = receive<String>(otherParty).unwrap { it }
|
val payload = otherPartySession.receive<String>().unwrap { it }
|
||||||
subFlow(InlinedSendFlow(payload + payload, otherParty))
|
subFlow(InlinedSendFlow(payload + payload, otherPartySession))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DoubleInlinedSubFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
private class DoubleInlinedSubFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
subFlow(SingleInlinedSubFlow(otherParty))
|
subFlow(SingleInlinedSubFlow(otherPartySession))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,9 @@ private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.
|
|||||||
}
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class AttachmentDemoFlow(val otherSide: Party, val notary: Party, val hash: SecureHash.SHA256) : FlowLogic<SignedTransaction>() {
|
class AttachmentDemoFlow(private val otherSide: Party,
|
||||||
|
private val notary: Party,
|
||||||
|
private val attachId: SecureHash.SHA256) : FlowLogic<SignedTransaction>() {
|
||||||
|
|
||||||
object SIGNING : ProgressTracker.Step("Signing transaction")
|
object SIGNING : ProgressTracker.Step("Signing transaction")
|
||||||
|
|
||||||
@ -116,16 +118,16 @@ class AttachmentDemoFlow(val otherSide: Party, val notary: Party, val hash: Secu
|
|||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
// Create a trivial transaction with an output that describes the attachment, and the attachment itself
|
// Create a trivial transaction with an output that describes the attachment, and the attachment itself
|
||||||
val ptx = TransactionBuilder(notary)
|
val ptx = TransactionBuilder(notary)
|
||||||
.addOutputState(AttachmentContract.State(hash), ATTACHMENT_PROGRAM_ID)
|
.addOutputState(AttachmentContract.State(attachId), ATTACHMENT_PROGRAM_ID)
|
||||||
.addCommand(AttachmentContract.Command, ourIdentity.owningKey)
|
.addCommand(AttachmentContract.Command, ourIdentity.owningKey)
|
||||||
.addAttachment(hash)
|
.addAttachment(attachId)
|
||||||
|
|
||||||
progressTracker.currentStep = SIGNING
|
progressTracker.currentStep = SIGNING
|
||||||
|
|
||||||
// Send the transaction to the other recipient
|
// Send the transaction to the other recipient
|
||||||
val stx = serviceHub.signInitialTransaction(ptx)
|
val stx = serviceHub.signInitialTransaction(ptx)
|
||||||
|
|
||||||
return subFlow(FinalityFlow(stx, setOf(otherSide))).single()
|
return subFlow(FinalityFlow(stx, setOf(otherSide)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import kotlin.test.assertTrue
|
|||||||
class BankOfCordaHttpAPITest {
|
class BankOfCordaHttpAPITest {
|
||||||
@Test
|
@Test
|
||||||
fun `issuer flow via Http`() {
|
fun `issuer flow via Http`() {
|
||||||
driver(dsl = {
|
driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = {
|
||||||
val bigCorpNodeFuture = startNode(providedName = BIGCORP_LEGAL_NAME)
|
val bigCorpNodeFuture = startNode(providedName = BIGCORP_LEGAL_NAME)
|
||||||
val nodeBankOfCordaFuture = startNode(providedName = BOC.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
val nodeBankOfCordaFuture = startNode(providedName = BOC.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
val (nodeBankOfCorda) = listOf(nodeBankOfCordaFuture, bigCorpNodeFuture).map { it.getOrThrow() }
|
val (nodeBankOfCorda) = listOf(nodeBankOfCordaFuture, bigCorpNodeFuture).map { it.getOrThrow() }
|
||||||
|
@ -18,7 +18,7 @@ import org.junit.Test
|
|||||||
class BankOfCordaRPCClientTest {
|
class BankOfCordaRPCClientTest {
|
||||||
@Test
|
@Test
|
||||||
fun `issuer flow via RPC`() {
|
fun `issuer flow via RPC`() {
|
||||||
driver(dsl = {
|
driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = {
|
||||||
val bocManager = User("bocManager", "password1", permissions = setOf(
|
val bocManager = User("bocManager", "password1", permissions = setOf(
|
||||||
startFlowPermission<CashIssueAndPaymentFlow>()))
|
startFlowPermission<CashIssueAndPaymentFlow>()))
|
||||||
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet())
|
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet())
|
||||||
|
@ -4,11 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.Command
|
import net.corda.core.contracts.Command
|
||||||
import net.corda.core.crypto.MerkleTreeException
|
import net.corda.core.crypto.MerkleTreeException
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.StartableByRPC
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.internal.ThreadBox
|
import net.corda.core.internal.ThreadBox
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
@ -46,17 +42,17 @@ import kotlin.collections.set
|
|||||||
object NodeInterestRates {
|
object NodeInterestRates {
|
||||||
// DOCSTART 2
|
// DOCSTART 2
|
||||||
@InitiatedBy(RatesFixFlow.FixSignFlow::class)
|
@InitiatedBy(RatesFixFlow.FixSignFlow::class)
|
||||||
class FixSignHandler(private val otherParty: Party) : FlowLogic<Unit>() {
|
class FixSignHandler(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val request = receive<RatesFixFlow.SignRequest>(otherParty).unwrap { it }
|
val request = otherPartySession.receive<RatesFixFlow.SignRequest>().unwrap { it }
|
||||||
val oracle = serviceHub.cordaService(Oracle::class.java)
|
val oracle = serviceHub.cordaService(Oracle::class.java)
|
||||||
send(otherParty, oracle.sign(request.ftx))
|
otherPartySession.send(oracle.sign(request.ftx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(RatesFixFlow.FixQueryFlow::class)
|
@InitiatedBy(RatesFixFlow.FixQueryFlow::class)
|
||||||
class FixQueryHandler(private val otherParty: Party) : FlowLogic<Unit>() {
|
class FixQueryHandler(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
object RECEIVED : ProgressTracker.Step("Received fix request")
|
object RECEIVED : ProgressTracker.Step("Received fix request")
|
||||||
object SENDING : ProgressTracker.Step("Sending fix response")
|
object SENDING : ProgressTracker.Step("Sending fix response")
|
||||||
|
|
||||||
@ -64,12 +60,12 @@ object NodeInterestRates {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val request = receive<RatesFixFlow.QueryRequest>(otherParty).unwrap { it }
|
val request = otherPartySession.receive<RatesFixFlow.QueryRequest>().unwrap { it }
|
||||||
progressTracker.currentStep = RECEIVED
|
progressTracker.currentStep = RECEIVED
|
||||||
val oracle = serviceHub.cordaService(Oracle::class.java)
|
val oracle = serviceHub.cordaService(Oracle::class.java)
|
||||||
val answers = oracle.query(request.queries)
|
val answers = oracle.query(request.queries)
|
||||||
progressTracker.currentStep = SENDING
|
progressTracker.currentStep = SENDING
|
||||||
send(otherParty, answers)
|
otherPartySession.send(answers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// DOCEND 2
|
// DOCEND 2
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
package net.corda.irs.flows
|
package net.corda.irs.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.StartableByRPC
|
|
||||||
import net.corda.core.identity.AbstractParty
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.finance.contracts.DealState
|
import net.corda.finance.contracts.DealState
|
||||||
@ -50,22 +45,19 @@ object AutoOfferFlow {
|
|||||||
require(serviceHub.networkMapCache.notaryIdentities.isNotEmpty()) { "No notary nodes registered" }
|
require(serviceHub.networkMapCache.notaryIdentities.isNotEmpty()) { "No notary nodes registered" }
|
||||||
val notary = serviceHub.networkMapCache.notaryIdentities.first() // TODO We should pass the notary as a parameter to the flow, not leave it to random choice.
|
val notary = serviceHub.networkMapCache.notaryIdentities.first() // TODO We should pass the notary as a parameter to the flow, not leave it to random choice.
|
||||||
// need to pick which ever party is not us
|
// need to pick which ever party is not us
|
||||||
val otherParty = notUs(dealToBeOffered.participants).map { serviceHub.identityService.partyFromAnonymous(it) }.requireNoNulls().single()
|
val otherParty = serviceHub.excludeMe(serviceHub.groupAbstractPartyByWellKnownParty(dealToBeOffered.participants)).keys.single()
|
||||||
progressTracker.currentStep = DEALING
|
progressTracker.currentStep = DEALING
|
||||||
|
val session = initiateFlow(otherParty)
|
||||||
val instigator = Instigator(
|
val instigator = Instigator(
|
||||||
otherParty,
|
session,
|
||||||
AutoOffer(notary, dealToBeOffered),
|
AutoOffer(notary, dealToBeOffered),
|
||||||
progressTracker.getChildProgressTracker(DEALING)!!
|
progressTracker.getChildProgressTracker(DEALING)!!
|
||||||
)
|
)
|
||||||
val stx = subFlow(instigator)
|
val stx = subFlow(instigator)
|
||||||
return stx
|
return stx
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : AbstractParty> notUs(parties: List<T>): List<T> {
|
|
||||||
return parties.filter { ourIdentity != it }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(Requester::class)
|
@InitiatedBy(Requester::class)
|
||||||
class AutoOfferAcceptor(otherParty: Party) : Acceptor(otherParty)
|
class AutoOfferAcceptor(otherSideSession: FlowSession) : Acceptor(otherSideSession)
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@ package net.corda.irs.flows
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.SchedulableFlow
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -28,7 +25,7 @@ object FixingFlow {
|
|||||||
* who does what in the flow.
|
* who does what in the flow.
|
||||||
*/
|
*/
|
||||||
@InitiatedBy(FixingRoleDecider::class)
|
@InitiatedBy(FixingRoleDecider::class)
|
||||||
class Fixer(override val otherParty: Party) : TwoPartyDealFlow.Secondary<FixingSession>() {
|
class Fixer(override val otherSideSession: FlowSession) : TwoPartyDealFlow.Secondary<FixingSession>() {
|
||||||
|
|
||||||
private lateinit var txState: TransactionState<*>
|
private lateinit var txState: TransactionState<*>
|
||||||
private lateinit var deal: FixableDealState
|
private lateinit var deal: FixableDealState
|
||||||
@ -91,7 +88,7 @@ object FixingFlow {
|
|||||||
* is just the "side" of the flow run by the party with the floating leg as a way of deciding who
|
* is just the "side" of the flow run by the party with the floating leg as a way of deciding who
|
||||||
* does what in the flow.
|
* does what in the flow.
|
||||||
*/
|
*/
|
||||||
class Floater(override val otherParty: Party,
|
class Floater(override val otherSideSession: FlowSession,
|
||||||
override val payload: FixingSession,
|
override val payload: FixingSession,
|
||||||
override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() {
|
override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -141,7 +138,8 @@ object FixingFlow {
|
|||||||
val fixing = FixingSession(ref, fixableDeal.oracle)
|
val fixing = FixingSession(ref, fixableDeal.oracle)
|
||||||
val counterparty = serviceHub.identityService.partyFromAnonymous(parties[1]) ?: throw IllegalStateException("Cannot resolve floater party")
|
val counterparty = serviceHub.identityService.partyFromAnonymous(parties[1]) ?: throw IllegalStateException("Cannot resolve floater party")
|
||||||
// Start the Floater which will then kick-off the Fixer
|
// Start the Floater which will then kick-off the Fixer
|
||||||
subFlow(Floater(counterparty, fixing))
|
val session = initiateFlow(counterparty)
|
||||||
|
subFlow(Floater(session, fixing))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -97,8 +98,9 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
|
|||||||
class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic<Fix>() {
|
class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic<Fix>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Fix {
|
override fun call(): Fix {
|
||||||
|
val oracleSession = initiateFlow(oracle)
|
||||||
// TODO: add deadline to receive
|
// TODO: add deadline to receive
|
||||||
val resp = sendAndReceive<ArrayList<Fix>>(oracle, QueryRequest(listOf(fixOf)))
|
val resp = oracleSession.sendAndReceive<ArrayList<Fix>>(QueryRequest(listOf(fixOf)))
|
||||||
|
|
||||||
return resp.unwrap {
|
return resp.unwrap {
|
||||||
val fix = it.first()
|
val fix = it.first()
|
||||||
@ -114,9 +116,10 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
|
|||||||
val partialMerkleTx: FilteredTransaction) : FlowLogic<TransactionSignature>() {
|
val partialMerkleTx: FilteredTransaction) : FlowLogic<TransactionSignature>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): TransactionSignature {
|
override fun call(): TransactionSignature {
|
||||||
val resp = sendAndReceive<TransactionSignature>(oracle, SignRequest(partialMerkleTx))
|
val oracleSession = initiateFlow(oracle)
|
||||||
|
val resp = oracleSession.sendAndReceive<TransactionSignature>(SignRequest(partialMerkleTx))
|
||||||
return resp.unwrap { sig ->
|
return resp.unwrap { sig ->
|
||||||
check(oracle.owningKey.isFulfilledBy(listOf(sig.by)))
|
check(oracleSession.counterparty.owningKey.isFulfilledBy(listOf(sig.by)))
|
||||||
tx.toWireTransaction().checkSignature(sig)
|
tx.toWireTransaction().checkSignature(sig)
|
||||||
sig
|
sig
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import net.corda.core.flows.InitiatedBy
|
|||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.flows.*
|
||||||
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -21,10 +23,10 @@ object UpdateBusinessDayFlow {
|
|||||||
data class UpdateBusinessDayMessage(val date: LocalDate)
|
data class UpdateBusinessDayMessage(val date: LocalDate)
|
||||||
|
|
||||||
@InitiatedBy(Broadcast::class)
|
@InitiatedBy(Broadcast::class)
|
||||||
private class UpdateBusinessDayHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
private class UpdateBusinessDayHandler(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val message = receive<UpdateBusinessDayMessage>(otherParty).unwrap { it }
|
val message = otherPartySession.receive<UpdateBusinessDayMessage>().unwrap { it }
|
||||||
(serviceHub.clock as TestClock).updateDate(message.date)
|
(serviceHub.clock as TestClock).updateDate(message.date)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +65,7 @@ object UpdateBusinessDayFlow {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun doNextRecipient(recipient: Party) {
|
private fun doNextRecipient(recipient: Party) {
|
||||||
send(recipient, UpdateBusinessDayMessage(date))
|
initiateFlow(recipient).send(UpdateBusinessDayMessage(date))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
|
|||||||
import net.corda.client.jackson.JacksonSupport
|
import net.corda.client.jackson.JacksonSupport
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
@ -143,11 +144,14 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
|||||||
class StartDealFlow(val otherParty: Party,
|
class StartDealFlow(val otherParty: Party,
|
||||||
val payload: AutoOffer) : FlowLogic<SignedTransaction>() {
|
val payload: AutoOffer) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction = subFlow(Instigator(otherParty, payload))
|
override fun call(): SignedTransaction {
|
||||||
|
val session = initiateFlow(otherParty)
|
||||||
|
return subFlow(Instigator(session, payload))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(StartDealFlow::class)
|
@InitiatedBy(StartDealFlow::class)
|
||||||
class AcceptDealFlow(otherParty: Party) : Acceptor(otherParty)
|
class AcceptDealFlow(otherSession: FlowSession) : Acceptor(otherSession)
|
||||||
|
|
||||||
val acceptDealFlows: Observable<AcceptDealFlow> = node2.internals.registerInitiatedFlow(AcceptDealFlow::class.java)
|
val acceptDealFlows: Observable<AcceptDealFlow> = node2.internals.registerInitiatedFlow(AcceptDealFlow::class.java)
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@ class IRSSimulationTest {
|
|||||||
LogHelper.setLevel("+messages") // FIXME: Don't manipulate static state in tests.
|
LogHelper.setLevel("+messages") // FIXME: Don't manipulate static state in tests.
|
||||||
val sim = IRSSimulation(false, false, null)
|
val sim = IRSSimulation(false, false, null)
|
||||||
val future = sim.start()
|
val future = sim.start()
|
||||||
while (!future.isDone) sim.iterate()
|
while (!future.isDone) {
|
||||||
|
sim.iterate()
|
||||||
|
}
|
||||||
future.getOrThrow()
|
future.getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package net.corda.vega.flows
|
package net.corda.vega.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.InitiatedBy
|
|
||||||
import net.corda.core.flows.InitiatingFlow
|
|
||||||
import net.corda.core.flows.StartableByRPC
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -33,28 +30,29 @@ object IRSTradeFlow {
|
|||||||
val offer = IRSState(swap, buyer, seller)
|
val offer = IRSState(swap, buyer, seller)
|
||||||
|
|
||||||
logger.info("Handshake finished, sending IRS trade offer message")
|
logger.info("Handshake finished, sending IRS trade offer message")
|
||||||
val otherPartyAgreeFlag = sendAndReceive<Boolean>(otherParty, OfferMessage(notary, offer)).unwrap { it }
|
val session = initiateFlow(otherParty)
|
||||||
|
val otherPartyAgreeFlag = session.sendAndReceive<Boolean>(OfferMessage(notary, offer)).unwrap { it }
|
||||||
require(otherPartyAgreeFlag)
|
require(otherPartyAgreeFlag)
|
||||||
|
|
||||||
return subFlow(TwoPartyDealFlow.Instigator(
|
return subFlow(TwoPartyDealFlow.Instigator(
|
||||||
otherParty,
|
session,
|
||||||
TwoPartyDealFlow.AutoOffer(notary, offer)))
|
TwoPartyDealFlow.AutoOffer(notary, offer)))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(Requester::class)
|
@InitiatedBy(Requester::class)
|
||||||
class Receiver(private val replyToParty: Party) : FlowLogic<Unit>() {
|
class Receiver(private val replyToSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
logger.info("IRSTradeFlow receiver started")
|
logger.info("IRSTradeFlow receiver started")
|
||||||
logger.info("Handshake finished, awaiting IRS trade offer")
|
logger.info("Handshake finished, awaiting IRS trade offer")
|
||||||
|
|
||||||
val offer = receive<OfferMessage>(replyToParty).unwrap { it }
|
val offer = replyToSession.receive<OfferMessage>().unwrap { it }
|
||||||
// Automatically agree - in reality we'd vet the offer message
|
// Automatically agree - in reality we'd vet the offer message
|
||||||
require(serviceHub.networkMapCache.notaryIdentities.contains(offer.notary))
|
require(serviceHub.networkMapCache.notaryIdentities.contains(offer.notary))
|
||||||
send(replyToParty, true)
|
replyToSession.send(true)
|
||||||
subFlow(TwoPartyDealFlow.Acceptor(replyToParty))
|
subFlow(TwoPartyDealFlow.Acceptor(replyToSession))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import net.corda.core.contracts.StateAndRef
|
|||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.AbstractStateReplacementFlow.Proposal
|
import net.corda.core.flows.AbstractStateReplacementFlow.Proposal
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.queryBy
|
import net.corda.core.node.services.queryBy
|
||||||
import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria
|
import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria
|
||||||
@ -57,6 +58,7 @@ object SimmFlow {
|
|||||||
: FlowLogic<RevisionedState<PortfolioState.Update>>() {
|
: FlowLogic<RevisionedState<PortfolioState.Update>>() {
|
||||||
constructor(otherParty: Party, valuationDate: LocalDate) : this(otherParty, valuationDate, null)
|
constructor(otherParty: Party, valuationDate: LocalDate) : this(otherParty, valuationDate, null)
|
||||||
lateinit var notary: Party
|
lateinit var notary: Party
|
||||||
|
lateinit var otherPartySession: FlowSession
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): RevisionedState<PortfolioState.Update> {
|
override fun call(): RevisionedState<PortfolioState.Update> {
|
||||||
@ -68,6 +70,7 @@ object SimmFlow {
|
|||||||
val trades = serviceHub.vaultQueryService.queryBy<IRSState>(criteria).states
|
val trades = serviceHub.vaultQueryService.queryBy<IRSState>(criteria).states
|
||||||
|
|
||||||
val portfolio = Portfolio(trades, valuationDate)
|
val portfolio = Portfolio(trades, valuationDate)
|
||||||
|
otherPartySession = initiateFlow(otherParty)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
agreePortfolio(portfolio)
|
agreePortfolio(portfolio)
|
||||||
} else {
|
} else {
|
||||||
@ -86,18 +89,24 @@ object SimmFlow {
|
|||||||
val parties = Pair(ourIdentity, otherParty)
|
val parties = Pair(ourIdentity, otherParty)
|
||||||
val portfolioState = PortfolioState(portfolio.refs, parties, valuationDate)
|
val portfolioState = PortfolioState(portfolio.refs, parties, valuationDate)
|
||||||
|
|
||||||
send(otherParty, OfferMessage(notary, portfolioState, existing?.ref, valuationDate))
|
otherPartySession.send(OfferMessage(notary, portfolioState, existing?.ref, valuationDate))
|
||||||
logger.info("Awaiting two party deal acceptor")
|
logger.info("Awaiting two party deal acceptor")
|
||||||
subFlow(TwoPartyDealFlow.Acceptor(otherParty))
|
subFlow(TwoPartyDealFlow.Acceptor(otherPartySession))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun updatePortfolio(portfolio: Portfolio, stateAndRef: StateAndRef<PortfolioState>) {
|
private fun updatePortfolio(portfolio: Portfolio, stateAndRef: StateAndRef<PortfolioState>) {
|
||||||
// Receive is a hack to ensure other side is ready
|
// Receive is a hack to ensure other side is ready
|
||||||
sendAndReceive<Ack>(otherParty, OfferMessage(notary, stateAndRef.state.data, existing?.ref, valuationDate))
|
otherPartySession.sendAndReceive<Ack>(OfferMessage(notary, stateAndRef.state.data, existing?.ref, valuationDate))
|
||||||
logger.info("Updating portfolio")
|
logger.info("Updating portfolio")
|
||||||
val update = PortfolioState.Update(portfolio = portfolio.refs)
|
val update = PortfolioState.Update(portfolio = portfolio.refs)
|
||||||
subFlow(StateRevisionFlow.Requester(stateAndRef, update))
|
subFlow(StateRevisionFlowRequester(otherPartySession, stateAndRef, update))
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StateRevisionFlowRequester<T>(val session: FlowSession, stateAndRef: StateAndRef<RevisionedState<T>>, update: T) : StateRevisionFlow.Requester<T>(stateAndRef, update) {
|
||||||
|
override fun getParticipantSessions(): List<Pair<FlowSession, List<AbstractParty>>> {
|
||||||
|
return listOf(session to listOf(session.counterparty))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -110,7 +119,7 @@ object SimmFlow {
|
|||||||
require(valuer != null) { "Valuer party must be known to this node" }
|
require(valuer != null) { "Valuer party must be known to this node" }
|
||||||
val valuation = agreeValuation(portfolio, valuationDate, valuer!!)
|
val valuation = agreeValuation(portfolio, valuationDate, valuer!!)
|
||||||
val update = PortfolioState.Update(valuation = valuation)
|
val update = PortfolioState.Update(valuation = valuation)
|
||||||
return subFlow(StateRevisionFlow.Requester(stateRef, update)).state.data
|
return subFlow(StateRevisionFlowRequester(otherPartySession, stateRef, update)).state.data
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -165,7 +174,7 @@ object SimmFlow {
|
|||||||
// TODO: In the real world, this would be tolerance aware for different types
|
// TODO: In the real world, this would be tolerance aware for different types
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private inline fun <reified T : Any> agree(data: T): Boolean {
|
private inline fun <reified T : Any> agree(data: T): Boolean {
|
||||||
val valid = receive<T>(otherParty).unwrap {
|
val valid = otherPartySession.receive<T>().unwrap {
|
||||||
logger.trace("Comparing --> $it")
|
logger.trace("Comparing --> $it")
|
||||||
logger.trace("with -------> $data")
|
logger.trace("with -------> $data")
|
||||||
if (it is InitialMarginTriple && data is InitialMarginTriple) {
|
if (it is InitialMarginTriple && data is InitialMarginTriple) {
|
||||||
@ -175,7 +184,7 @@ object SimmFlow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.trace("valid is $valid")
|
logger.trace("valid is $valid")
|
||||||
send(otherParty, valid)
|
otherPartySession.send(valid)
|
||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,16 +193,16 @@ object SimmFlow {
|
|||||||
* Receives and validates a portfolio and comes to consensus over the portfolio initial margin using SIMM.
|
* Receives and validates a portfolio and comes to consensus over the portfolio initial margin using SIMM.
|
||||||
*/
|
*/
|
||||||
@InitiatedBy(Requester::class)
|
@InitiatedBy(Requester::class)
|
||||||
class Receiver(val replyToParty: Party) : FlowLogic<Unit>() {
|
class Receiver(val replyToSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
lateinit var offer: OfferMessage
|
lateinit var offer: OfferMessage
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val criteria = LinearStateQueryCriteria(participants = listOf(replyToParty))
|
val criteria = LinearStateQueryCriteria(participants = listOf(replyToSession.counterparty))
|
||||||
val trades = serviceHub.vaultQueryService.queryBy<IRSState>(criteria).states
|
val trades = serviceHub.vaultQueryService.queryBy<IRSState>(criteria).states
|
||||||
val portfolio = Portfolio(trades)
|
val portfolio = Portfolio(trades)
|
||||||
logger.info("SimmFlow receiver started")
|
logger.info("SimmFlow receiver started")
|
||||||
offer = receive<OfferMessage>(replyToParty).unwrap { it }
|
offer = replyToSession.receive<OfferMessage>().unwrap { it }
|
||||||
if (offer.stateRef == null) {
|
if (offer.stateRef == null) {
|
||||||
agreePortfolio(portfolio)
|
agreePortfolio(portfolio)
|
||||||
} else {
|
} else {
|
||||||
@ -205,8 +214,8 @@ object SimmFlow {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun agree(data: Any): Boolean {
|
private fun agree(data: Any): Boolean {
|
||||||
send(replyToParty, data)
|
replyToSession.send(data)
|
||||||
return receive<Boolean>(replyToParty).unwrap { it }
|
return replyToSession.receive<Boolean>().unwrap { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -287,17 +296,17 @@ object SimmFlow {
|
|||||||
require(offer.dealBeingOffered.portfolio == portfolio.refs)
|
require(offer.dealBeingOffered.portfolio == portfolio.refs)
|
||||||
|
|
||||||
val seller = TwoPartyDealFlow.Instigator(
|
val seller = TwoPartyDealFlow.Instigator(
|
||||||
replyToParty,
|
replyToSession,
|
||||||
TwoPartyDealFlow.AutoOffer(offer.notary, offer.dealBeingOffered))
|
TwoPartyDealFlow.AutoOffer(offer.notary, offer.dealBeingOffered))
|
||||||
logger.info("Starting two party deal initiator with: ${replyToParty.name}")
|
logger.info("Starting two party deal initiator with: ${replyToSession.counterparty.name}")
|
||||||
return subFlow(seller)
|
return subFlow(seller)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun updatePortfolio(portfolio: Portfolio) {
|
private fun updatePortfolio(portfolio: Portfolio) {
|
||||||
logger.info("Handshake finished, awaiting Simm update")
|
logger.info("Handshake finished, awaiting Simm update")
|
||||||
send(replyToParty, Ack) // Hack to state that this party is ready.
|
replyToSession.send(Ack) // Hack to state that this party is ready.
|
||||||
subFlow(object : StateRevisionFlow.Receiver<PortfolioState.Update>(replyToParty) {
|
subFlow(object : StateRevisionFlow.Receiver<PortfolioState.Update>(replyToSession) {
|
||||||
override fun verifyProposal(stx:SignedTransaction, proposal: Proposal<PortfolioState.Update>) {
|
override fun verifyProposal(stx:SignedTransaction, proposal: Proposal<PortfolioState.Update>) {
|
||||||
super.verifyProposal(stx, proposal)
|
super.verifyProposal(stx, proposal)
|
||||||
if (proposal.modification.portfolio != portfolio.refs) throw StateReplacementException()
|
if (proposal.modification.portfolio != portfolio.refs) throw StateReplacementException()
|
||||||
@ -310,7 +319,7 @@ object SimmFlow {
|
|||||||
val portfolio = serviceHub.vaultQueryService.queryBy<IRSState>(VaultQueryCriteria(stateRefs = stateRef.state.data.portfolio)).states.toPortfolio()
|
val portfolio = serviceHub.vaultQueryService.queryBy<IRSState>(VaultQueryCriteria(stateRefs = stateRef.state.data.portfolio)).states.toPortfolio()
|
||||||
val valuer = serviceHub.identityService.partyFromAnonymous(stateRef.state.data.valuer) ?: throw IllegalStateException("Unknown valuer party ${stateRef.state.data.valuer}")
|
val valuer = serviceHub.identityService.partyFromAnonymous(stateRef.state.data.valuer) ?: throw IllegalStateException("Unknown valuer party ${stateRef.state.data.valuer}")
|
||||||
val valuation = agreeValuation(portfolio, offer.valuationDate, valuer)
|
val valuation = agreeValuation(portfolio, offer.valuationDate, valuer)
|
||||||
subFlow(object : StateRevisionFlow.Receiver<PortfolioState.Update>(replyToParty) {
|
subFlow(object : StateRevisionFlow.Receiver<PortfolioState.Update>(replyToSession) {
|
||||||
override fun verifyProposal(stx: SignedTransaction, proposal: Proposal<PortfolioState.Update>) {
|
override fun verifyProposal(stx: SignedTransaction, proposal: Proposal<PortfolioState.Update>) {
|
||||||
super.verifyProposal(stx, proposal)
|
super.verifyProposal(stx, proposal)
|
||||||
if (proposal.modification.valuation != valuation) throw StateReplacementException()
|
if (proposal.modification.valuation != valuation) throw StateReplacementException()
|
||||||
|
@ -3,8 +3,8 @@ package net.corda.vega.flows
|
|||||||
import net.corda.core.contracts.PrivacySalt
|
import net.corda.core.contracts.PrivacySalt
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.flows.AbstractStateReplacementFlow
|
import net.corda.core.flows.AbstractStateReplacementFlow
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.StateReplacementException
|
import net.corda.core.flows.StateReplacementException
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.vega.contracts.RevisionedState
|
import net.corda.vega.contracts.RevisionedState
|
||||||
@ -14,7 +14,7 @@ import net.corda.vega.contracts.RevisionedState
|
|||||||
* on the update between two parties.
|
* on the update between two parties.
|
||||||
*/
|
*/
|
||||||
object StateRevisionFlow {
|
object StateRevisionFlow {
|
||||||
class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
|
open class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
|
||||||
updatedData: T) : AbstractStateReplacementFlow.Instigator<RevisionedState<T>, RevisionedState<T>, T>(curStateRef, updatedData) {
|
updatedData: T) : AbstractStateReplacementFlow.Instigator<RevisionedState<T>, RevisionedState<T>, T>(curStateRef, updatedData) {
|
||||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||||
val state = originalState.state.data
|
val state = originalState.state.data
|
||||||
@ -22,16 +22,12 @@ object StateRevisionFlow {
|
|||||||
tx.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
tx.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
||||||
val privacySalt = PrivacySalt()
|
val privacySalt = PrivacySalt()
|
||||||
tx.setPrivacySalt(privacySalt)
|
tx.setPrivacySalt(privacySalt)
|
||||||
|
|
||||||
val stx = serviceHub.signInitialTransaction(tx)
|
val stx = serviceHub.signInitialTransaction(tx)
|
||||||
val participantKeys = state.participants.map { it.owningKey }
|
return AbstractStateReplacementFlow.UpgradeTx(stx)
|
||||||
// TODO: We need a much faster way of finding our key in the transaction
|
|
||||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
|
||||||
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Receiver<in T>(otherParty: Party) : AbstractStateReplacementFlow.Acceptor<T>(otherParty) {
|
open class Receiver<in T>(initiatingSession: FlowSession) : AbstractStateReplacementFlow.Acceptor<T>(initiatingSession) {
|
||||||
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal<T>) {
|
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal<T>) {
|
||||||
val proposedTx = stx.tx
|
val proposedTx = stx.tx
|
||||||
val state = proposal.stateRef
|
val state = proposal.stateRef
|
||||||
|
@ -9,14 +9,10 @@ import net.corda.finance.flows.CashPaymentFlow
|
|||||||
import net.corda.finance.schemas.CashSchemaV1
|
import net.corda.finance.schemas.CashSchemaV1
|
||||||
import net.corda.finance.schemas.CommercialPaperSchemaV1
|
import net.corda.finance.schemas.CommercialPaperSchemaV1
|
||||||
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
||||||
import net.corda.nodeapi.ServiceInfo
|
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
|
import net.corda.nodeapi.ServiceInfo
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.BOC
|
import net.corda.testing.*
|
||||||
import net.corda.testing.DUMMY_BANK_A
|
|
||||||
import net.corda.testing.DUMMY_BANK_B
|
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.driver.poll
|
import net.corda.testing.driver.poll
|
||||||
import net.corda.testing.node.NodeBasedTest
|
import net.corda.testing.node.NodeBasedTest
|
||||||
import net.corda.traderdemo.flow.BuyerFlow
|
import net.corda.traderdemo.flow.BuyerFlow
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.traderdemo.flow
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
@ -16,7 +17,7 @@ import net.corda.traderdemo.TransactionGraphSearch
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@InitiatedBy(SellerFlow::class)
|
@InitiatedBy(SellerFlow::class)
|
||||||
class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
class BuyerFlow(private val otherSideSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
|
||||||
object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset")
|
object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset")
|
||||||
|
|
||||||
@ -27,11 +28,11 @@ class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
|||||||
progressTracker.currentStep = STARTING_BUY
|
progressTracker.currentStep = STARTING_BUY
|
||||||
|
|
||||||
// Receive the offered amount and automatically agree to it (in reality this would be a longer negotiation)
|
// Receive the offered amount and automatically agree to it (in reality this would be a longer negotiation)
|
||||||
val amount = receive<Amount<Currency>>(otherParty).unwrap { it }
|
val amount = otherSideSession.receive<Amount<Currency>>().unwrap { it }
|
||||||
require(serviceHub.networkMapCache.notaryIdentities.isNotEmpty()) { "No notary nodes registered" }
|
require(serviceHub.networkMapCache.notaryIdentities.isNotEmpty()) { "No notary nodes registered" }
|
||||||
val notary: Party = serviceHub.networkMapCache.notaryIdentities.first()
|
val notary: Party = serviceHub.networkMapCache.notaryIdentities.first()
|
||||||
val buyer = TwoPartyTradeFlow.Buyer(
|
val buyer = TwoPartyTradeFlow.Buyer(
|
||||||
otherParty,
|
otherSideSession,
|
||||||
notary,
|
notary,
|
||||||
amount,
|
amount,
|
||||||
CommercialPaper.State::class.java)
|
CommercialPaper.State::class.java)
|
||||||
|
@ -54,7 +54,7 @@ class CommercialPaperIssueFlow(private val amount: Amount<Currency>,
|
|||||||
// Sign it as ourselves.
|
// Sign it as ourselves.
|
||||||
val stx = serviceHub.signInitialTransaction(tx)
|
val stx = serviceHub.signInitialTransaction(tx)
|
||||||
|
|
||||||
subFlow(FinalityFlow(stx)).single()
|
subFlow(FinalityFlow(stx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
|
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
|
||||||
@ -62,10 +62,9 @@ class CommercialPaperIssueFlow(private val amount: Amount<Currency>,
|
|||||||
val builder = TransactionBuilder(notary)
|
val builder = TransactionBuilder(notary)
|
||||||
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), recipient)
|
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), recipient)
|
||||||
val stx = serviceHub.signInitialTransaction(builder)
|
val stx = serviceHub.signInitialTransaction(builder)
|
||||||
subFlow(FinalityFlow(stx)).single()
|
subFlow(FinalityFlow(stx))
|
||||||
}
|
}
|
||||||
|
|
||||||
return move
|
return move
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,9 +45,10 @@ class SellerFlow(private val otherParty: Party,
|
|||||||
progressTracker.currentStep = TRADING
|
progressTracker.currentStep = TRADING
|
||||||
|
|
||||||
// Send the offered amount.
|
// Send the offered amount.
|
||||||
send(otherParty, amount)
|
val session = initiateFlow(otherParty)
|
||||||
|
session.send(amount)
|
||||||
val seller = TwoPartyTradeFlow.Seller(
|
val seller = TwoPartyTradeFlow.Seller(
|
||||||
otherParty,
|
session,
|
||||||
commercialPaper,
|
commercialPaper,
|
||||||
amount,
|
amount,
|
||||||
cpOwner,
|
cpOwner,
|
||||||
|
@ -225,6 +225,7 @@ fun <A> rpcDriver(
|
|||||||
initialiseSerialization: Boolean = true,
|
initialiseSerialization: Boolean = true,
|
||||||
networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false),
|
networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false),
|
||||||
startNodesInProcess: Boolean = false,
|
startNodesInProcess: Boolean = false,
|
||||||
|
extraCordappPackagesToScan: List<String> = emptyList(),
|
||||||
dsl: RPCDriverExposedDSLInterface.() -> A
|
dsl: RPCDriverExposedDSLInterface.() -> A
|
||||||
) = genericDriver(
|
) = genericDriver(
|
||||||
driverDsl = RPCDriverDSL(
|
driverDsl = RPCDriverDSL(
|
||||||
@ -236,7 +237,8 @@ fun <A> rpcDriver(
|
|||||||
useTestClock = useTestClock,
|
useTestClock = useTestClock,
|
||||||
networkMapStartStrategy = networkMapStartStrategy,
|
networkMapStartStrategy = networkMapStartStrategy,
|
||||||
isDebug = isDebug,
|
isDebug = isDebug,
|
||||||
startNodesInProcess = startNodesInProcess
|
startNodesInProcess = startNodesInProcess,
|
||||||
|
extraCordappPackagesToScan = extraCordappPackagesToScan
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
coerce = { it },
|
coerce = { it },
|
||||||
|
@ -310,6 +310,7 @@ fun <A> driver(
|
|||||||
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
|
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
|
||||||
networkMapStartStrategy: NetworkMapStartStrategy = defaultParameters.networkMapStartStrategy,
|
networkMapStartStrategy: NetworkMapStartStrategy = defaultParameters.networkMapStartStrategy,
|
||||||
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
|
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
|
||||||
|
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
|
||||||
dsl: DriverDSLExposedInterface.() -> A
|
dsl: DriverDSLExposedInterface.() -> A
|
||||||
): A {
|
): A {
|
||||||
return genericDriver(
|
return genericDriver(
|
||||||
@ -319,9 +320,10 @@ fun <A> driver(
|
|||||||
systemProperties = systemProperties,
|
systemProperties = systemProperties,
|
||||||
driverDirectory = driverDirectory.toAbsolutePath(),
|
driverDirectory = driverDirectory.toAbsolutePath(),
|
||||||
useTestClock = useTestClock,
|
useTestClock = useTestClock,
|
||||||
|
isDebug = isDebug,
|
||||||
networkMapStartStrategy = networkMapStartStrategy,
|
networkMapStartStrategy = networkMapStartStrategy,
|
||||||
startNodesInProcess = startNodesInProcess,
|
startNodesInProcess = startNodesInProcess,
|
||||||
isDebug = isDebug
|
extraCordappPackagesToScan = extraCordappPackagesToScan
|
||||||
),
|
),
|
||||||
coerce = { it },
|
coerce = { it },
|
||||||
dsl = dsl,
|
dsl = dsl,
|
||||||
@ -355,7 +357,8 @@ data class DriverParameters(
|
|||||||
val useTestClock: Boolean = false,
|
val useTestClock: Boolean = false,
|
||||||
val initialiseSerialization: Boolean = true,
|
val initialiseSerialization: Boolean = true,
|
||||||
val networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true),
|
val networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true),
|
||||||
val startNodesInProcess: Boolean = false
|
val startNodesInProcess: Boolean = false,
|
||||||
|
val extraCordappPackagesToScan: List<String> = emptyList()
|
||||||
) {
|
) {
|
||||||
fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug)
|
fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug)
|
||||||
fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory)
|
fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory)
|
||||||
@ -366,6 +369,7 @@ data class DriverParameters(
|
|||||||
fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization)
|
fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization)
|
||||||
fun setNetworkMapStartStrategy(networkMapStartStrategy: NetworkMapStartStrategy) = copy(networkMapStartStrategy = networkMapStartStrategy)
|
fun setNetworkMapStartStrategy(networkMapStartStrategy: NetworkMapStartStrategy) = copy(networkMapStartStrategy = networkMapStartStrategy)
|
||||||
fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess)
|
fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess)
|
||||||
|
fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List<String>) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -579,14 +583,15 @@ class DriverDSL(
|
|||||||
val useTestClock: Boolean,
|
val useTestClock: Boolean,
|
||||||
val isDebug: Boolean,
|
val isDebug: Boolean,
|
||||||
val networkMapStartStrategy: NetworkMapStartStrategy,
|
val networkMapStartStrategy: NetworkMapStartStrategy,
|
||||||
val startNodesInProcess: Boolean
|
val startNodesInProcess: Boolean,
|
||||||
|
val extraCordappPackagesToScan: List<String>
|
||||||
) : DriverDSLInternalInterface {
|
) : DriverDSLInternalInterface {
|
||||||
private val dedicatedNetworkMapAddress = portAllocation.nextHostAndPort()
|
private val dedicatedNetworkMapAddress = portAllocation.nextHostAndPort()
|
||||||
private var _executorService: ScheduledExecutorService? = null
|
private var _executorService: ScheduledExecutorService? = null
|
||||||
val executorService get() = _executorService!!
|
val executorService get() = _executorService!!
|
||||||
private var _shutdownManager: ShutdownManager? = null
|
private var _shutdownManager: ShutdownManager? = null
|
||||||
override val shutdownManager get() = _shutdownManager!!
|
override val shutdownManager get() = _shutdownManager!!
|
||||||
private val callerPackage = getCallerPackage()
|
private val packagesToScanString = (extraCordappPackagesToScan + getCallerPackage()).joinToString(",")
|
||||||
|
|
||||||
class State {
|
class State {
|
||||||
val processes = ArrayList<CordaFuture<Process>>()
|
val processes = ArrayList<CordaFuture<Process>>()
|
||||||
@ -786,7 +791,7 @@ class DriverDSL(
|
|||||||
_executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build())
|
_executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build())
|
||||||
_shutdownManager = ShutdownManager(executorService)
|
_shutdownManager = ShutdownManager(executorService)
|
||||||
// We set this property so that in-process nodes find cordapps. Out-of-process nodes need this passed in when started.
|
// We set this property so that in-process nodes find cordapps. Out-of-process nodes need this passed in when started.
|
||||||
System.setProperty("net.corda.node.cordapp.scan.package", callerPackage)
|
System.setProperty("net.corda.node.cordapp.scan.packages", packagesToScanString)
|
||||||
if (networkMapStartStrategy.startDedicated) {
|
if (networkMapStartStrategy.startDedicated) {
|
||||||
startDedicatedNetworkMapService().andForget(log) // Allow it to start concurrently with other nodes.
|
startDedicatedNetworkMapService().andForget(log) // Allow it to start concurrently with other nodes.
|
||||||
}
|
}
|
||||||
@ -840,7 +845,7 @@ class DriverDSL(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||||
val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, callerPackage)
|
val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, packagesToScanString)
|
||||||
registerProcess(processFuture)
|
registerProcess(processFuture)
|
||||||
return processFuture.flatMap { process ->
|
return processFuture.flatMap { process ->
|
||||||
val processDeathFuture = poll(executorService, "process death") {
|
val processDeathFuture = poll(executorService, "process death") {
|
||||||
@ -904,7 +909,7 @@ class DriverDSL(
|
|||||||
quasarJarPath: String,
|
quasarJarPath: String,
|
||||||
debugPort: Int?,
|
debugPort: Int?,
|
||||||
overriddenSystemProperties: Map<String, String>,
|
overriddenSystemProperties: Map<String, String>,
|
||||||
callerPackage: String
|
packagesToScanString: String
|
||||||
): CordaFuture<Process> {
|
): CordaFuture<Process> {
|
||||||
val processFuture = executorService.fork {
|
val processFuture = executorService.fork {
|
||||||
log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}")
|
log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}")
|
||||||
@ -914,7 +919,7 @@ class DriverDSL(
|
|||||||
val systemProperties = overriddenSystemProperties + mapOf(
|
val systemProperties = overriddenSystemProperties + mapOf(
|
||||||
"name" to nodeConf.myLegalName,
|
"name" to nodeConf.myLegalName,
|
||||||
"visualvm.display.name" to "corda-${nodeConf.myLegalName}",
|
"visualvm.display.name" to "corda-${nodeConf.myLegalName}",
|
||||||
"net.corda.node.cordapp.scan.package" to callerPackage,
|
"net.corda.node.cordapp.scan.packages" to packagesToScanString,
|
||||||
"java.io.tmpdir" to System.getProperty("java.io.tmpdir") // Inherit from parent process
|
"java.io.tmpdir" to System.getProperty("java.io.tmpdir") // Inherit from parent process
|
||||||
)
|
)
|
||||||
// See experimental/quasar-hook/README.md for how to generate.
|
// See experimental/quasar-hook/README.md for how to generate.
|
||||||
|
@ -76,6 +76,7 @@ fun <A> verifierDriver(
|
|||||||
useTestClock: Boolean = false,
|
useTestClock: Boolean = false,
|
||||||
networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false),
|
networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false),
|
||||||
startNodesInProcess: Boolean = false,
|
startNodesInProcess: Boolean = false,
|
||||||
|
extraCordappPackagesToScan: List<String> = emptyList(),
|
||||||
dsl: VerifierExposedDSLInterface.() -> A
|
dsl: VerifierExposedDSLInterface.() -> A
|
||||||
) = genericDriver(
|
) = genericDriver(
|
||||||
driverDsl = VerifierDriverDSL(
|
driverDsl = VerifierDriverDSL(
|
||||||
@ -87,7 +88,8 @@ fun <A> verifierDriver(
|
|||||||
useTestClock = useTestClock,
|
useTestClock = useTestClock,
|
||||||
networkMapStartStrategy = networkMapStartStrategy,
|
networkMapStartStrategy = networkMapStartStrategy,
|
||||||
isDebug = isDebug,
|
isDebug = isDebug,
|
||||||
startNodesInProcess = startNodesInProcess
|
startNodesInProcess = startNodesInProcess,
|
||||||
|
extraCordappPackagesToScan = extraCordappPackagesToScan
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
coerce = { it },
|
coerce = { it },
|
||||||
|
Loading…
Reference in New Issue
Block a user