mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +00:00
Enable anonymisation in two party deal/trade flows
This commit is contained in:
parent
a84cd567d8
commit
bc5aceddbf
@ -4,11 +4,9 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.flows.CollectSignaturesFlow
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.SignTransactionFlow
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
@ -29,9 +27,14 @@ import java.security.PublicKey
|
||||
// TODO: Also, the term Deal is used here where we might prefer Agreement.
|
||||
// TODO: Make this flow more generic.
|
||||
object TwoPartyDealFlow {
|
||||
// This object is serialised to the network and is the first flow message the seller sends to the buyer.
|
||||
/**
|
||||
* This object is serialised to the network and is the first flow message the seller sends to the buyer.
|
||||
*
|
||||
* @param primaryIdentity the (anonymised) identity of the participant that initiates communication/handshake.
|
||||
* @param secondaryIdentity the (anonymised) identity of the participant that is recipient of initial communication.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class Handshake<out T>(val payload: T, val publicKey: PublicKey)
|
||||
data class Handshake<out T>(val payload: T, val primaryIdentity: AnonymousParty, val secondaryIdentity: AnonymousParty)
|
||||
|
||||
/**
|
||||
* Abstracted bilateral deal flow participant that initiates communication/handshake.
|
||||
@ -39,19 +42,25 @@ object TwoPartyDealFlow {
|
||||
abstract class Primary(override val progressTracker: ProgressTracker = Primary.tracker()) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
object GENERATING_ID : ProgressTracker.Step("Generating anonymous identities")
|
||||
object SENDING_PROPOSAL : ProgressTracker.Step("Handshaking and awaiting transaction proposal.")
|
||||
fun tracker() = ProgressTracker(SENDING_PROPOSAL)
|
||||
fun tracker() = ProgressTracker(GENERATING_ID, SENDING_PROPOSAL)
|
||||
}
|
||||
|
||||
abstract val payload: Any
|
||||
abstract val notaryNode: NodeInfo
|
||||
abstract val otherParty: Party
|
||||
// TODO: This is never read from, and should be removed
|
||||
abstract val myKey: PublicKey
|
||||
|
||||
@Suspendable override fun call(): SignedTransaction {
|
||||
progressTracker.currentStep = GENERATING_ID
|
||||
val txIdentities = subFlow(TransactionKeyFlow(otherParty))
|
||||
val anonymousMe = txIdentities.get(serviceHub.myInfo.legalIdentity) ?: serviceHub.myInfo.legalIdentity.anonymise()
|
||||
val anonymousCounterparty = txIdentities.get(otherParty) ?: otherParty.anonymise()
|
||||
progressTracker.currentStep = SENDING_PROPOSAL
|
||||
// Make the first message we'll send to kick off the flow.
|
||||
val hello = Handshake(payload, serviceHub.myInfo.legalIdentity.owningKey)
|
||||
val hello = Handshake(payload, anonymousMe, anonymousCounterparty)
|
||||
// Wait for the FinalityFlow to finish on the other side and return the tx when it's available.
|
||||
send(otherParty, hello)
|
||||
|
||||
@ -105,7 +114,7 @@ object TwoPartyDealFlow {
|
||||
progressTracker.currentStep = COLLECTING_SIGNATURES
|
||||
|
||||
// DOCSTART 1
|
||||
val stx = subFlow(CollectSignaturesFlow(ptx))
|
||||
val stx = subFlow(CollectSignaturesFlow(ptx, additionalSigningPubKeys))
|
||||
// DOCEND 1
|
||||
|
||||
logger.trace { "Got signatures from other party, verifying ... " }
|
||||
@ -138,7 +147,14 @@ object TwoPartyDealFlow {
|
||||
val handshake = receive<Handshake<U>>(otherParty)
|
||||
|
||||
progressTracker.currentStep = VERIFYING
|
||||
return handshake.unwrap { validateHandshake(it) }
|
||||
return handshake.unwrap {
|
||||
// Verify the transaction identities represent the correct parties
|
||||
val wellKnownOtherParty = serviceHub.identityService.partyFromAnonymous(it.primaryIdentity)
|
||||
val wellKnownMe = serviceHub.identityService.partyFromAnonymous(it.secondaryIdentity)
|
||||
require(wellKnownOtherParty == otherParty)
|
||||
require(wellKnownMe == serviceHub.myInfo.legalIdentity)
|
||||
validateHandshake(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable protected abstract fun validateHandshake(handshake: Handshake<U>): Handshake<U>
|
||||
@ -155,7 +171,6 @@ object TwoPartyDealFlow {
|
||||
override val payload: AutoOffer,
|
||||
override val myKey: PublicKey,
|
||||
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary() {
|
||||
|
||||
override val notaryNode: NodeInfo get() =
|
||||
serviceHub.networkMapCache.notaryNodes.filter { it.notaryIdentity == payload.notary }.single()
|
||||
|
||||
|
@ -5,10 +5,10 @@ import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.OwnableState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.withoutIssuer
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -48,18 +48,22 @@ object TwoPartyTradeFlow {
|
||||
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
|
||||
}
|
||||
|
||||
// This object is serialised to the network and is the first flow message the seller sends to the buyer.
|
||||
/**
|
||||
* This object is serialised to the network and is the first flow message the seller sends to the buyer.
|
||||
*
|
||||
* @param payToIdentity anonymous identity of the seller, for payment to be sent to.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class SellerTradeInfo(
|
||||
val price: Amount<Currency>,
|
||||
val sellerOwner: AbstractParty
|
||||
val payToIdentity: PartyAndCertificate
|
||||
)
|
||||
|
||||
open class Seller(val otherParty: Party,
|
||||
val notaryNode: NodeInfo,
|
||||
val assetToSell: StateAndRef<OwnableState>,
|
||||
val price: Amount<Currency>,
|
||||
val me: AbstractParty,
|
||||
val me: PartyAndCertificate,
|
||||
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
@ -84,12 +88,26 @@ object TwoPartyTradeFlow {
|
||||
// SendTransactionFlow allows otherParty to access our data to resolve the transaction.
|
||||
subFlow(SendStateAndRefFlow(otherParty, listOf(assetToSell)))
|
||||
send(otherParty, hello)
|
||||
|
||||
// Verify and sign the transaction.
|
||||
progressTracker.currentStep = VERIFYING_AND_SIGNING
|
||||
|
||||
// Sync identities to ensure we know all of the identities involved in the transaction we're about to
|
||||
// be asked to sign
|
||||
subFlow(IdentitySyncFlow.Receive(otherParty))
|
||||
|
||||
// DOCSTART 5
|
||||
val signTransactionFlow = object : SignTransactionFlow(otherParty, VERIFYING_AND_SIGNING.childProgressTracker()) {
|
||||
override fun checkTransaction(stx: SignedTransaction) {
|
||||
if (stx.tx.outputStates.sumCashBy(me).withoutIssuer() != price)
|
||||
// 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 })
|
||||
states.forEach { state ->
|
||||
state.participants.forEach { anon ->
|
||||
require(serviceHub.identityService.partyFromAnonymous(anon) != null) { "Transaction state ${state} involves unknown participant ${anon}" }
|
||||
}
|
||||
}
|
||||
|
||||
if (stx.tx.outputStates.sumCashBy(me.party).withoutIssuer() != price)
|
||||
throw FlowException("Transaction is not sending us the right amount of cash")
|
||||
}
|
||||
}
|
||||
@ -114,7 +132,9 @@ object TwoPartyTradeFlow {
|
||||
open class Buyer(val otherParty: Party,
|
||||
val notary: Party,
|
||||
val acceptablePrice: Amount<Currency>,
|
||||
val typeToBuy: Class<out OwnableState>) : FlowLogic<SignedTransaction>() {
|
||||
val typeToBuy: Class<out OwnableState>,
|
||||
val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
|
||||
constructor(otherParty: Party, notary: Party, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>): this(otherParty, notary, acceptablePrice, typeToBuy, true)
|
||||
// DOCSTART 2
|
||||
object RECEIVING : ProgressTracker.Step("Waiting for seller trading info")
|
||||
|
||||
@ -139,16 +159,27 @@ object TwoPartyTradeFlow {
|
||||
progressTracker.currentStep = RECEIVING
|
||||
val (assetForSale, tradeRequest) = receiveAndValidateTradeRequest()
|
||||
|
||||
// Create the identity we'll be paying to, and send the counterparty proof we own the identity
|
||||
val buyerAnonymousIdentity = if (anonymous)
|
||||
serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false)
|
||||
else
|
||||
serviceHub.myInfo.legalIdentityAndCert
|
||||
|
||||
// Put together a proposed transaction that performs the trade, and sign it.
|
||||
progressTracker.currentStep = SIGNING
|
||||
val (ptx, cashSigningPubKeys) = assembleSharedTX(assetForSale, tradeRequest)
|
||||
val (ptx, cashSigningPubKeys) = assembleSharedTX(assetForSale, tradeRequest, buyerAnonymousIdentity)
|
||||
|
||||
// Now sign the transaction with whatever keys we need to move the cash.
|
||||
val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
|
||||
|
||||
// Sync up confidential identities in the transaction with our counterparty
|
||||
subFlow(IdentitySyncFlow.Send(otherParty, ptx.toWireTransaction()))
|
||||
|
||||
// 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.
|
||||
progressTracker.currentStep = COLLECTING_SIGNATURES
|
||||
val twiceSignedTx = subFlow(CollectSignaturesFlow(partSignedTx, COLLECTING_SIGNATURES.childProgressTracker()))
|
||||
val twiceSignedTx = subFlow(CollectSignaturesFlow(partSignedTx, cashSigningPubKeys, COLLECTING_SIGNATURES.childProgressTracker()))
|
||||
|
||||
// Notarise and record the transaction.
|
||||
progressTracker.currentStep = RECORDING
|
||||
return subFlow(FinalityFlow(twiceSignedTx)).single()
|
||||
@ -159,33 +190,40 @@ object TwoPartyTradeFlow {
|
||||
val assetForSale = subFlow(ReceiveStateAndRefFlow<OwnableState>(otherParty)).single()
|
||||
return assetForSale to receive<SellerTradeInfo>(otherParty).unwrap {
|
||||
progressTracker.currentStep = VERIFYING
|
||||
// What is the seller trying to sell us?
|
||||
val asset = assetForSale.state.data
|
||||
val assetTypeName = asset.javaClass.name
|
||||
|
||||
// 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.
|
||||
val assetForSaleIdentity = serviceHub.identityService.partyFromAnonymous(asset.owner)
|
||||
require(assetForSaleIdentity == otherParty)
|
||||
|
||||
// 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.
|
||||
val wellKnownPayToIdentity = serviceHub.identityService.verifyAndRegisterIdentity(it.payToIdentity)
|
||||
require(wellKnownPayToIdentity?.party == otherParty) { "Well known identity to pay to must match counterparty identity" }
|
||||
|
||||
if (it.price > acceptablePrice)
|
||||
throw UnacceptablePriceException(it.price)
|
||||
if (!typeToBuy.isInstance(asset))
|
||||
throw AssetMismatchException(typeToBuy.name, assetTypeName)
|
||||
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suspendable
|
||||
private fun assembleSharedTX(assetForSale: StateAndRef<OwnableState>, tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
private fun assembleSharedTX(assetForSale: StateAndRef<OwnableState>, tradeRequest: SellerTradeInfo, buyerAnonymousIdentity: PartyAndCertificate): SharedTx {
|
||||
val ptx = TransactionBuilder(notary)
|
||||
|
||||
// Add input and output states for the movement of cash, by using the Cash contract to generate the states
|
||||
val (tx, cashSigningPubKeys) = Cash.generateSpend(serviceHub, ptx, tradeRequest.price, tradeRequest.sellerOwner)
|
||||
val (tx, cashSigningPubKeys) = Cash.generateSpend(serviceHub, ptx, tradeRequest.price, tradeRequest.payToIdentity.party)
|
||||
|
||||
// Add inputs/outputs/a command for the movement of the asset.
|
||||
tx.addInputState(assetForSale)
|
||||
|
||||
// Just pick some new public key for now. This won't be linked with our identity in any way, which is what
|
||||
// we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to
|
||||
// reveal who the owner actually is. The key management service is expected to derive a unique key from some
|
||||
// initial seed in order to provide privacy protection.
|
||||
val freshKey = serviceHub.keyManagementService.freshKey()
|
||||
val (command, state) = assetForSale.state.data.withNewOwner(AnonymousParty(freshKey))
|
||||
val (command, state) = assetForSale.state.data.withNewOwner(buyerAnonymousIdentity.party)
|
||||
tx.addOutputState(state, assetForSale.state.notary)
|
||||
tx.addCommand(command, assetForSale.state.data.owner.owningKey)
|
||||
|
||||
@ -193,8 +231,11 @@ object TwoPartyTradeFlow {
|
||||
// But it can't hurt to have one.
|
||||
val currentTime = serviceHub.clock.instant()
|
||||
tx.setTimeWindow(currentTime, 30.seconds)
|
||||
return Pair(tx, cashSigningPubKeys)
|
||||
|
||||
return SharedTx(tx, cashSigningPubKeys)
|
||||
}
|
||||
// DOCEND 1
|
||||
|
||||
data class SharedTx(val tx: TransactionBuilder, val cashSigningPubKeys: List<PublicKey>)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.concurrent.map
|
||||
import net.corda.core.internal.rootCause
|
||||
@ -20,6 +21,7 @@ import net.corda.core.messaging.StateMachineTransactionMapping
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -145,19 +147,18 @@ class TwoPartyTradeFlowTests {
|
||||
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name)
|
||||
val cashIssuer = bankNode.info.legalIdentity.ref(1)
|
||||
val cpIssuer = bankNode.info.legalIdentity.ref(1, 2, 3)
|
||||
val issuer = bankNode.info.legalIdentity.ref(1)
|
||||
|
||||
aliceNode.disableDBCloseOnStop()
|
||||
bobNode.disableDBCloseOnStop()
|
||||
|
||||
val cashStates = bobNode.database.transaction {
|
||||
bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, notaryNode.info.notaryIdentity, 3, 3,
|
||||
issuedBy = cashIssuer)
|
||||
issuedBy = issuer)
|
||||
}
|
||||
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, cpIssuer, aliceNode.info.legalIdentity,
|
||||
fillUpForSeller(false, issuer, aliceNode.info.legalIdentity,
|
||||
1200.DOLLARS `issued by` bankNode.info.legalIdentity.ref(0), null, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
|
||||
@ -199,8 +200,13 @@ class TwoPartyTradeFlowTests {
|
||||
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
|
||||
var bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name)
|
||||
val cashIssuer = bankNode.info.legalIdentity.ref(1)
|
||||
val cpIssuer = bankNode.info.legalIdentity.ref(1, 2, 3)
|
||||
val issuer = bankNode.info.legalIdentity.ref(1, 2, 3)
|
||||
|
||||
// Let the nodes know about each other - normally the network map would handle this
|
||||
val allNodes = listOf(notaryNode, aliceNode, bobNode, bankNode)
|
||||
allNodes.forEach { node ->
|
||||
allNodes.map { it.services.myInfo.legalIdentityAndCert }.forEach { identity -> node.services.identityService.registerIdentity(identity) }
|
||||
}
|
||||
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
@ -214,10 +220,10 @@ class TwoPartyTradeFlowTests {
|
||||
|
||||
bobNode.database.transaction {
|
||||
bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, outputNotary = notaryNode.info.notaryIdentity,
|
||||
issuedBy = cashIssuer)
|
||||
issuedBy = issuer)
|
||||
}
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, cpIssuer, aliceNode.info.legalIdentity,
|
||||
fillUpForSeller(false, issuer, aliceNode.info.legalIdentity,
|
||||
1200.DOLLARS `issued by` bankNode.info.legalIdentity.ref(0), null, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode)
|
||||
@ -253,6 +259,9 @@ class TwoPartyTradeFlowTests {
|
||||
// She will wait around until Bob comes back.
|
||||
assertThat(aliceNode.pumpReceive()).isNotNull()
|
||||
|
||||
// FIXME: Knowledge of confidential identities is lost on node shutdown, so Bob's node now refuses to sign the
|
||||
// transaction because it has no idea who the parties are.
|
||||
|
||||
// ... bring the node back up ... the act of constructing the SMM will re-register the message handlers
|
||||
// that Bob was waiting on before the reboot occurred.
|
||||
bobNode = mockNet.createNode(networkMapAddress, bobAddr.id, object : MockNetwork.Factory<MockNetwork.MockNode> {
|
||||
@ -437,7 +446,6 @@ class TwoPartyTradeFlowTests {
|
||||
}
|
||||
|
||||
ledger(aliceNode.services, initialiseSerialization = false) {
|
||||
|
||||
// Insert a prospectus type attachment into the commercial paper transaction.
|
||||
val stream = ByteArrayOutputStream()
|
||||
JarOutputStream(stream).use {
|
||||
@ -529,13 +537,11 @@ class TwoPartyTradeFlowTests {
|
||||
private fun runBuyerAndSeller(notaryNode: MockNetwork.MockNode,
|
||||
sellerNode: MockNetwork.MockNode,
|
||||
buyerNode: MockNetwork.MockNode,
|
||||
assetToSell: StateAndRef<OwnableState>): RunResult {
|
||||
val anonymousSeller = sellerNode.services.let { serviceHub ->
|
||||
serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false)
|
||||
}.party.anonymise()
|
||||
val buyerFlows: Observable<BuyerAcceptor> = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java)
|
||||
assetToSell: StateAndRef<OwnableState>,
|
||||
anonymous: Boolean = true): RunResult {
|
||||
val buyerFlows: Observable<out FlowLogic<*>> = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java)
|
||||
val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine }
|
||||
val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller)
|
||||
val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymous)
|
||||
val sellerResult = sellerNode.services.startFlow(seller).resultFuture
|
||||
return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id)
|
||||
}
|
||||
@ -545,10 +551,15 @@ class TwoPartyTradeFlowTests {
|
||||
val notary: NodeInfo,
|
||||
val assetToSell: StateAndRef<OwnableState>,
|
||||
val price: Amount<Currency>,
|
||||
val me: AnonymousParty) : FlowLogic<SignedTransaction>() {
|
||||
val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
send(buyer, Pair(notary.notaryIdentity, price))
|
||||
val me = if (anonymous) {
|
||||
serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false)
|
||||
} else {
|
||||
serviceHub.myInfo.legalIdentityAndCert
|
||||
}
|
||||
send(buyer, TestTx(notary.notaryIdentity, price, anonymous))
|
||||
return subFlow(Seller(
|
||||
buyer,
|
||||
notary,
|
||||
@ -562,14 +573,17 @@ class TwoPartyTradeFlowTests {
|
||||
class BuyerAcceptor(val seller: Party) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
val (notary, price) = receive<Pair<Party, Amount<Currency>>>(seller).unwrap {
|
||||
require(serviceHub.networkMapCache.isNotary(it.first)) { "${it.first} is not a notary" }
|
||||
val (notary, price, anonymous) = receive<TestTx>(seller).unwrap {
|
||||
require(serviceHub.networkMapCache.isNotary(it.notaryIdentity)) { "${it.notaryIdentity} is not a notary" }
|
||||
it
|
||||
}
|
||||
return subFlow(Buyer(seller, notary, price, CommercialPaper.State::class.java))
|
||||
return subFlow(Buyer(seller, notary, price, CommercialPaper.State::class.java, anonymous))
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
data class TestTx(val notaryIdentity: Party, val price: Amount<Currency>, val anonymous: Boolean)
|
||||
|
||||
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
|
||||
bobError: Boolean,
|
||||
aliceError: Boolean,
|
||||
@ -581,6 +595,12 @@ class TwoPartyTradeFlowTests {
|
||||
val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name)
|
||||
val issuer = bankNode.info.legalIdentity.ref(1, 2, 3)
|
||||
|
||||
// Let the nodes know about each other - normally the network map would handle this
|
||||
val allNodes = listOf(notaryNode, aliceNode, bobNode, bankNode)
|
||||
allNodes.forEach { node ->
|
||||
allNodes.map { it.services.myInfo.legalIdentityAndCert }.forEach { identity -> node.services.identityService.registerIdentity(identity) }
|
||||
}
|
||||
|
||||
val bobsBadCash = bobNode.database.transaction {
|
||||
fillUpForBuyer(bobError, issuer, bobNode.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity).second
|
||||
|
@ -8,6 +8,7 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.SchedulableFlow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
@ -110,8 +111,7 @@ object FixingFlow {
|
||||
}
|
||||
|
||||
override val myKey: PublicKey get() {
|
||||
dealToFix.state.data.participants.single { it.owningKey == serviceHub.myInfo.legalIdentity.owningKey }
|
||||
return serviceHub.legalIdentityKey
|
||||
return serviceHub.keyManagementService.filterMyKeys(dealToFix.state.data.participants.map(AbstractParty::owningKey)).single()
|
||||
}
|
||||
|
||||
override val notaryNode: NodeInfo get() {
|
||||
|
@ -6,7 +6,6 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -42,10 +41,9 @@ class SellerFlow(val otherParty: Party,
|
||||
progressTracker.currentStep = SELF_ISSUING
|
||||
|
||||
val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
|
||||
val cpOwnerKey = serviceHub.keyManagementService.freshKey()
|
||||
val cpOwner = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false)
|
||||
val commercialPaper = serviceHub.vaultQueryService.queryBy(CommercialPaper.State::class.java).states.first()
|
||||
|
||||
|
||||
progressTracker.currentStep = TRADING
|
||||
|
||||
// Send the offered amount.
|
||||
@ -55,7 +53,7 @@ class SellerFlow(val otherParty: Party,
|
||||
notary,
|
||||
commercialPaper,
|
||||
amount,
|
||||
AnonymousParty(cpOwnerKey),
|
||||
cpOwner,
|
||||
progressTracker.getChildProgressTracker(TRADING)!!)
|
||||
return subFlow(seller)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user