mirror of
https://github.com/corda/corda.git
synced 2025-01-26 22:29:28 +00:00
De-issuerify the cash payment flow. This makes it easier to use the payment flow from the shell.
This commit is contained in:
parent
4f6b44ceff
commit
afbc8f9b5c
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)!!
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 -> {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user