De-issuerify the cash payment flow. This makes it easier to use the payment flow from the shell.

This commit is contained in:
Mike Hearn 2017-03-15 18:12:28 +01:00
parent 4f6b44ceff
commit afbc8f9b5c
15 changed files with 43 additions and 72 deletions

View File

@ -1,7 +1,6 @@
package net.corda.client package net.corda.client
import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.issuedBy
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
@ -85,10 +84,7 @@ class CordaRPCClientTest : NodeBasedTest() {
fun `FlowException thrown by flow`() { fun `FlowException thrown by flow`() {
client.start(rpcUser.username, rpcUser.password) client.start(rpcUser.username, rpcUser.password)
val proxy = client.proxy() val proxy = client.proxy()
val handle = proxy.startFlow(::CashPaymentFlow, val handle = proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.legalIdentity)
100.DOLLARS.issuedBy(node.info.legalIdentity.ref(1)),
node.info.legalIdentity
)
// TODO Restrict this to CashException once RPC serialisation has been fixed // TODO Restrict this to CashException once RPC serialisation has been fixed
assertThatExceptionOfType(FlowException::class.java).isThrownBy { assertThatExceptionOfType(FlowException::class.java).isThrownBy {
handle.returnValue.getOrThrow() handle.returnValue.getOrThrow()

View File

@ -4,8 +4,7 @@ import net.corda.client.model.NodeMonitorModel
import net.corda.client.model.ProgressTrackingEvent import net.corda.client.model.ProgressTrackingEvent
import net.corda.core.bufferUntilSubscribed import net.corda.core.bufferUntilSubscribed
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.USD import net.corda.core.contracts.USD
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
@ -123,17 +122,8 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test @Test
fun `cash issue and move`() { fun `cash issue and move`() {
rpc.startFlow(::CashIssueFlow, rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), aliceNode.legalIdentity, notaryNode.notaryIdentity).returnValue.getOrThrow()
Amount(100, USD), rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.legalIdentity).returnValue.getOrThrow()
OpaqueBytes(ByteArray(1, { 1 })),
aliceNode.legalIdentity,
notaryNode.notaryIdentity
).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow,
Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
aliceNode.legalIdentity
)
var issueSmId: StateMachineRunId? = null var issueSmId: StateMachineRunId? = null
var moveSmId: StateMachineRunId? = null var moveSmId: StateMachineRunId? = null

View File

@ -73,11 +73,9 @@ class EventGenerator(
} }
val moveCashGenerator = val moveCashGenerator =
amountIssuedGenerator.combine( amountIssuedGenerator.combine(partyGenerator) { amountIssued, recipient ->
partyGenerator
) { amountIssued, recipient ->
CashFlowCommand.PayCash( CashFlowCommand.PayCash(
amount = amountIssued, amount = amountIssued.withoutIssuer(),
recipient = recipient recipient = recipient
) )
} }

View File

@ -3,9 +3,7 @@ package net.corda.core.contracts
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException() { class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing")
override fun toString() = "Insufficient balance, missing $amountMissing"
}
/** /**
* Interface for contract states representing assets which are fungible, countable and issued by a * Interface for contract states representing assets which are fungible, countable and issued by a

View File

@ -4,13 +4,11 @@ import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.issuedBy
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.toFuture
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow import net.corda.flows.CashPaymentFlow
import net.corda.node.driver.driver import net.corda.node.driver.driver
@ -92,10 +90,7 @@ class IntegrationTestingTutorial {
// START 5 // START 5
for (i in 1 .. 10) { for (i in 1 .. 10) {
bobProxy.startFlow(::CashPaymentFlow, bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.legalIdentity).returnValue.getOrThrow()
i.DOLLARS.issuedBy(alice.nodeInfo.legalIdentity.ref(issueRef)),
alice.nodeInfo.legalIdentity
).returnValue.getOrThrow()
} }
aliceVaultUpdates.expectEvents { aliceVaultUpdates.expectEvents {

View File

@ -122,7 +122,7 @@ fun generateTransactions(proxy: CordaRPCOps) {
ownedQuantity -= quantity ownedQuantity -= quantity
} else if (ownedQuantity > 1000 && n < 0.7) { } else if (ownedQuantity > 1000 && n < 0.7) {
val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000)) val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000))
proxy.startFlow(::CashPaymentFlow, Amount(quantity, Issued(meAndRef, USD)), me) proxy.startFlow(::CashPaymentFlow, Amount(quantity, USD), me)
} else { } else {
val quantity = Math.abs(random.nextLong() % 1000) val quantity = Math.abs(random.nextLong() % 1000)
proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, me, notary) proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, me, notary)

View File

@ -1,7 +1,6 @@
package net.corda.flows package net.corda.flows
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
@ -11,7 +10,7 @@ import net.corda.core.transactions.SignedTransaction
import java.util.* import java.util.*
/** /**
* A command to initiate the Cash flow with. * A command to initiate the cash flow with.
*/ */
sealed class CashFlowCommand { sealed class CashFlowCommand {
abstract fun startFlow(proxy: CordaRPCOps): FlowHandle<SignedTransaction> abstract fun startFlow(proxy: CordaRPCOps): FlowHandle<SignedTransaction>
@ -32,7 +31,7 @@ sealed class CashFlowCommand {
* @param amount the amount of currency to issue on to the ledger. * @param amount the amount of currency to issue on to the ledger.
* @param recipient the party to issue the cash to. * @param recipient the party to issue the cash to.
*/ */
class PayCash(val amount: Amount<Issued<Currency>>, val recipient: Party) : CashFlowCommand() { class PayCash(val amount: Amount<Currency>, val recipient: Party, val issuerConstraint: Party? = null) : CashFlowCommand() {
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashPaymentFlow, amount, recipient) override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashPaymentFlow, amount, recipient)
} }

View File

@ -1,7 +1,9 @@
package net.corda.flows package net.corda.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.* import net.corda.core.contracts.Amount
import net.corda.core.contracts.InsufficientBalanceException
import net.corda.core.contracts.TransactionType
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.crypto.keys import net.corda.core.crypto.keys
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
@ -12,13 +14,19 @@ import java.security.KeyPair
import java.util.* import java.util.*
/** /**
* Initiates a flow that produces an cash move transaction. * Initiates a flow that sends cash to a recipient.
* *
* @param amount the amount of a currency to pay to the recipient. * @param amount the amount of a currency to pay to the recipient.
* @param recipient the party to pay the currency to. * @param recipient the party to pay the currency to.
* @param issuerConstraint if specified, the payment will be made using only cash issued by the given parties.
*/ */
open class CashPaymentFlow(val amount: Amount<Issued<Currency>>, val recipient: Party, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { open class CashPaymentFlow(
constructor(amount: Amount<Issued<Currency>>, recipient: Party) : this(amount, recipient, tracker()) val amount: Amount<Currency>,
val recipient: Party,
progressTracker: ProgressTracker,
val issuerConstraint: Set<Party>? = null) : AbstractCashFlow(progressTracker) {
/** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, tracker())
@Suspendable @Suspendable
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
@ -28,11 +36,11 @@ open class CashPaymentFlow(val amount: Amount<Issued<Currency>>, val recipient:
val (spendTX, keysForSigning) = try { val (spendTX, keysForSigning) = try {
serviceHub.vaultService.generateSpend( serviceHub.vaultService.generateSpend(
builder, builder,
amount.withoutIssuer(), amount,
recipient.owningKey, recipient.owningKey,
setOf(amount.token.issuer.party)) issuerConstraint)
} catch (e: InsufficientBalanceException) { } catch (e: InsufficientBalanceException) {
throw CashException("Insufficient cash for spend", e) throw CashException("Insufficient cash for spend: ${e.message}", e)
} }
progressTracker.currentStep = SIGNING_TX progressTracker.currentStep = SIGNING_TX

View File

@ -89,7 +89,7 @@ object IssuerFlow {
return issueTx return issueTx
// now invoke Cash subflow to Move issued assetType to issue requester // now invoke Cash subflow to Move issued assetType to issue requester
progressTracker.currentStep = TRANSFERRING progressTracker.currentStep = TRANSFERRING
val moveCashFlow = CashPaymentFlow(amount.issuedBy(bankOfCordaParty.ref(issuerPartyRef)), issueTo) val moveCashFlow = CashPaymentFlow(amount, issueTo)
val moveTx = subFlow(moveCashFlow) val moveTx = subFlow(moveCashFlow)
// NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger) // NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger)
return moveTx return moveTx

View File

@ -144,9 +144,7 @@ class DistributedServiceTests : DriverBasedTest() {
} }
private fun paySelf(amount: Amount<Currency>) { private fun paySelf(amount: Amount<Currency>) {
val payHandle = aliceProxy.startFlow( val payHandle = aliceProxy.startFlow(::CashPaymentFlow, amount, alice.nodeInfo.legalIdentity)
::CashPaymentFlow,
amount.issuedBy(alice.nodeInfo.legalIdentity.ref(0)), alice.nodeInfo.legalIdentity)
payHandle.returnValue.getOrThrow() payHandle.returnValue.getOrThrow()
} }
} }

View File

@ -126,10 +126,7 @@ class CordaRPCOpsImplTest {
network.runNetwork() network.runNetwork()
rpc.startFlow(::CashPaymentFlow, rpc.startFlow(::CashPaymentFlow, Amount(100, USD), aliceNode.info.legalIdentity)
Amount(100, Issued(PartyAndReference(aliceNode.info.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
aliceNode.info.legalIdentity
)
network.runNetwork() network.runNetwork()

View File

@ -6,7 +6,6 @@ import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.* import net.corda.core.*
import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.DummyState import net.corda.core.contracts.DummyState
import net.corda.core.contracts.issuedBy
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
@ -326,9 +325,7 @@ class StateMachineManagerTests {
notary1.info.notaryIdentity)) notary1.info.notaryIdentity))
// We pay a couple of times, the notary picking should go round robin // We pay a couple of times, the notary picking should go round robin
for (i in 1 .. 3) { for (i in 1 .. 3) {
node1.services.startFlow(CashPaymentFlow( node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity))
500.DOLLARS.issuedBy(node1.info.legalIdentity.ref(0x01)),
node2.info.legalIdentity))
net.runNetwork() net.runNetwork()
} }
val endpoint = net.messagingNetwork.endpoint(notary1.net.myAddress as InMemoryMessagingNetwork.PeerHandle)!! val endpoint = net.messagingNetwork.endpoint(notary1.net.myAddress as InMemoryMessagingNetwork.PeerHandle)!!

View File

@ -18,8 +18,6 @@ import net.corda.client.fxutils.map
import net.corda.client.fxutils.unique import net.corda.client.fxutils.unique
import net.corda.client.model.* import net.corda.client.model.*
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.withoutIssuer import net.corda.core.contracts.withoutIssuer
import net.corda.core.crypto.AbstractParty import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
@ -148,7 +146,7 @@ class NewTransaction : Fragment() {
CashTransaction.Issue -> { CashTransaction.Issue -> {
CashFlowCommand.IssueCash(Amount(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity) CashFlowCommand.IssueCash(Amount(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity)
} }
CashTransaction.Pay -> CashFlowCommand.PayCash(Amount(amount.value, Issued(PartyAndReference(issuerChoiceBox.value, issueRef), currencyChoiceBox.value)), partyBChoiceBox.value.legalIdentity) CashTransaction.Pay -> CashFlowCommand.PayCash(Amount(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.legalIdentity)
CashTransaction.Exit -> CashFlowCommand.ExitCash(Amount(amount.value, currencyChoiceBox.value), issueRef) CashTransaction.Exit -> CashFlowCommand.ExitCash(Amount(amount.value, currencyChoiceBox.value), issueRef)
else -> null else -> null
} }

View File

@ -3,16 +3,13 @@ package net.corda.loadtest.tests
import net.corda.client.mock.Generator import net.corda.client.mock.Generator
import net.corda.client.mock.pickN import net.corda.client.mock.pickN
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.core.*
import net.corda.core.contracts.Issued import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.USD import net.corda.core.contracts.USD
import net.corda.core.crypto.AbstractParty import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.AnonymousParty import net.corda.core.failure
import net.corda.core.flows.FlowException
import net.corda.core.messaging.startFlow
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashException import net.corda.core.success
import net.corda.flows.CashFlowCommand import net.corda.flows.CashFlowCommand
import net.corda.loadtest.LoadTest import net.corda.loadtest.LoadTest
import net.corda.loadtest.NodeHandle import net.corda.loadtest.NodeHandle
@ -125,7 +122,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
val quantities = state.nodeVaults[node.info.legalIdentity] ?: mapOf() val quantities = state.nodeVaults[node.info.legalIdentity] ?: mapOf()
val possibleRecipients = nodeMap.keys.toList() val possibleRecipients = nodeMap.keys.toList()
val moves = quantities.map { val moves = quantities.map {
it.value.toDouble() / 1000 to generateMove(it.value, USD, it.key.toAnonymous(), possibleRecipients) it.value.toDouble() / 1000 to generateMove(it.value, USD, node.info.legalIdentity, possibleRecipients)
} }
val exits = quantities.mapNotNull { val exits = quantities.mapNotNull {
if (it.key == node.info.legalIdentity) { if (it.key == node.info.legalIdentity) {
@ -160,26 +157,26 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
val newDiffQueues = state.copyQueues() val newDiffQueues = state.copyQueues()
val recipientOriginators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() }) val recipientOriginators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() })
val senderQuantities = newNodeVaults[command.node.info.legalIdentity]!! val senderQuantities = newNodeVaults[command.node.info.legalIdentity]!!
val quantity = command.command.amount.quantity val amount = command.command.amount
val issuer = command.command.amount.token.issuer.party val issuer = command.command.issuerConstraint!!
val originator = command.node.info.legalIdentity val originator = command.node.info.legalIdentity
val senderQuantity = senderQuantities[issuer] ?: throw Exception( val senderQuantity = senderQuantities[issuer] ?: throw Exception(
"Generated payment of ${command.command.amount} from ${command.node.info.legalIdentity}, " + "Generated payment of ${command.command.amount} from ${command.node.info.legalIdentity}, " +
"however there is no cash from $issuer!" "however there is no cash from $issuer!"
) )
if (senderQuantity < quantity) { if (senderQuantity < amount.quantity) {
throw Exception( throw Exception(
"Generated payment of ${command.command.amount} from ${command.node.info.legalIdentity}, " + "Generated payment of ${command.command.amount} from ${command.node.info.legalIdentity}, " +
"however they only have $senderQuantity!" "however they only have $senderQuantity!"
) )
} }
if (senderQuantity == quantity) { if (senderQuantity == amount.quantity) {
senderQuantities.remove(issuer) senderQuantities.remove(issuer)
} else { } else {
senderQuantities.put(issuer, senderQuantity - quantity) senderQuantities.put(issuer, senderQuantity - amount.quantity)
} }
val recipientQueue = recipientOriginators.getOrPut(originator, { ArrayList() }) val recipientQueue = recipientOriginators.getOrPut(originator, { ArrayList() })
recipientQueue.add(Pair(issuer, quantity)) recipientQueue.add(Pair(issuer, amount.quantity))
CrossCashState(newNodeVaults, newDiffQueues) CrossCashState(newNodeVaults, newDiffQueues)
} }
is CashFlowCommand.ExitCash -> { is CashFlowCommand.ExitCash -> {

View File

@ -5,7 +5,7 @@ import net.corda.client.mock.generateAmount
import net.corda.client.mock.pickOne import net.corda.client.mock.pickOne
import net.corda.core.contracts.Issued import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.AnonymousParty import net.corda.core.contracts.withoutIssuer
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashFlowCommand import net.corda.flows.CashFlowCommand
@ -28,13 +28,13 @@ fun generateIssue(
fun generateMove( fun generateMove(
max: Long, max: Long,
currency: Currency, currency: Currency,
issuer: AnonymousParty, issuer: Party,
possibleRecipients: List<Party> possibleRecipients: List<Party>
): Generator<CashFlowCommand.PayCash> { ): Generator<CashFlowCommand.PayCash> {
return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine( return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine(
Generator.pickOne(possibleRecipients) Generator.pickOne(possibleRecipients)
) { amount, recipient -> ) { amount, recipient ->
CashFlowCommand.PayCash(amount, recipient) CashFlowCommand.PayCash(amount.withoutIssuer(), recipient, issuer)
} }
} }