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

View File

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

View File

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

View File

@ -3,9 +3,7 @@ package net.corda.core.contracts
import net.corda.core.crypto.CompositeKey
import net.corda.core.flows.FlowException
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException() {
override fun toString() = "Insufficient balance, missing $amountMissing"
}
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing")
/**
* 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 net.corda.contracts.asset.Cash
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.issuedBy
import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.toFuture
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.driver.driver
@ -92,10 +90,7 @@ class IntegrationTestingTutorial {
// START 5
for (i in 1 .. 10) {
bobProxy.startFlow(::CashPaymentFlow,
i.DOLLARS.issuedBy(alice.nodeInfo.legalIdentity.ref(issueRef)),
alice.nodeInfo.legalIdentity
).returnValue.getOrThrow()
bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.legalIdentity).returnValue.getOrThrow()
}
aliceVaultUpdates.expectEvents {

View File

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

View File

@ -1,7 +1,6 @@
package net.corda.flows
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.crypto.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle
@ -11,7 +10,7 @@ import net.corda.core.transactions.SignedTransaction
import java.util.*
/**
* A command to initiate the Cash flow with.
* A command to initiate the cash flow with.
*/
sealed class CashFlowCommand {
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 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)
}

View File

@ -1,7 +1,9 @@
package net.corda.flows
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.keys
import net.corda.core.crypto.toStringShort
@ -12,13 +14,19 @@ import java.security.KeyPair
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 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) {
constructor(amount: Amount<Issued<Currency>>, recipient: Party) : this(amount, recipient, tracker())
open class CashPaymentFlow(
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
override fun call(): SignedTransaction {
@ -28,11 +36,11 @@ open class CashPaymentFlow(val amount: Amount<Issued<Currency>>, val recipient:
val (spendTX, keysForSigning) = try {
serviceHub.vaultService.generateSpend(
builder,
amount.withoutIssuer(),
amount,
recipient.owningKey,
setOf(amount.token.issuer.party))
issuerConstraint)
} catch (e: InsufficientBalanceException) {
throw CashException("Insufficient cash for spend", e)
throw CashException("Insufficient cash for spend: ${e.message}", e)
}
progressTracker.currentStep = SIGNING_TX

View File

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

View File

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

View File

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

View File

@ -6,7 +6,6 @@ import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.*
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.DummyState
import net.corda.core.contracts.issuedBy
import net.corda.core.crypto.Party
import net.corda.core.crypto.generateKeyPair
import net.corda.core.flows.FlowException
@ -326,9 +325,7 @@ class StateMachineManagerTests {
notary1.info.notaryIdentity))
// We pay a couple of times, the notary picking should go round robin
for (i in 1 .. 3) {
node1.services.startFlow(CashPaymentFlow(
500.DOLLARS.issuedBy(node1.info.legalIdentity.ref(0x01)),
node2.info.legalIdentity))
node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity))
net.runNetwork()
}
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.model.*
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.crypto.AbstractParty
import net.corda.core.crypto.Party
@ -148,7 +146,7 @@ class NewTransaction : Fragment() {
CashTransaction.Issue -> {
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)
else -> null
}

View File

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

View File

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