Removing usages of the deprecated CashFlowCommand (#1289)

* Removing usages of the deprecated CashFlowCommand

* Addressing review comments
This commit is contained in:
mkit 2017-08-21 13:32:08 +01:00 committed by GitHub
parent b5d844c4ca
commit 2744079b4b
13 changed files with 224 additions and 120 deletions

View File

@ -5,8 +5,10 @@ import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.core.identity.Party
import net.corda.core.utilities.OpaqueBytes
import net.corda.flows.CashFlowCommand
import java.util.*
import net.corda.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.flows.CashExitFlow.ExitRequest
import net.corda.flows.CashPaymentFlow.PaymentRequest
/**
* [Generator]s for incoming/outgoing cash flow events between parties. It doesn't necessarily generate correct events!
@ -26,16 +28,16 @@ open class EventGenerator(val parties: List<Party>, val currencies: List<Currenc
protected val issueCashGenerator = amountGenerator.combine(partyGenerator, issueRefGenerator, currencyGenerator) { amount, to, issueRef, ccy ->
addToMap(ccy, amount)
CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary, anonymous = true)
IssueAndPaymentRequest(Amount(amount, ccy), issueRef, to, notary, anonymous = true)
}
protected val exitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator) { amount, issueRef, ccy ->
addToMap(ccy, -amount)
CashFlowCommand.ExitCash(Amount(amount, ccy), issueRef)
ExitRequest(Amount(amount, ccy), issueRef)
}
open val moveCashGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient, anonymous = true)
PaymentRequest(Amount(amountIssued, currency), recipient, anonymous = true)
}
open val issuerGenerator = Generator.frequency(listOf(
@ -54,28 +56,28 @@ class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>,
EXIT_ERROR
}
val errorGenerator = Generator.pickOne(IssuerEvents.values().toList())
private val errorGenerator = Generator.pickOne(IssuerEvents.values().toList())
val errorExitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator, errorGenerator) { amount, issueRef, ccy, errorType ->
private val errorExitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator, errorGenerator) { amount, issueRef, ccy, errorType ->
when (errorType) {
IssuerEvents.NORMAL_EXIT -> {
println("Normal exit")
if (currencyMap[ccy]!! <= amount) addToMap(ccy, -amount)
CashFlowCommand.ExitCash(Amount(amount, ccy), issueRef) // It may fail at the beginning, but we don't care.
ExitRequest(Amount(amount, ccy), issueRef) // It may fail at the beginning, but we don't care.
}
IssuerEvents.EXIT_ERROR -> {
println("Exit error")
CashFlowCommand.ExitCash(Amount(currencyMap[ccy]!! * 2, ccy), issueRef)
ExitRequest(Amount(currencyMap[ccy]!! * 2, ccy), issueRef)
}
}
}
val normalMoveGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient, anonymous = true)
private val normalMoveGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
PaymentRequest(Amount(amountIssued, currency), recipient, anonymous = true)
}
val errorMoveGenerator = partyGenerator.combine(currencyGenerator) { recipient, currency ->
CashFlowCommand.PayCash(Amount(currencyMap[currency]!! * 2, currency), recipient, anonymous = true)
private val errorMoveGenerator = partyGenerator.combine(currencyGenerator) { recipient, currency ->
PaymentRequest(Amount(currencyMap[currency]!! * 2, currency), recipient, anonymous = true)
}
override val moveCashGenerator = Generator.frequency(listOf(

View File

@ -1,6 +1,7 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
@ -10,6 +11,7 @@ import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import java.util.*
/**
* Initiates a flow that produces an Issue/Move or Exit Cash transaction.
@ -44,6 +46,8 @@ abstract class AbstractCashFlow<out T>(override val progressTracker: ProgressTra
*/
@CordaSerializable
data class Result(val stx: SignedTransaction, val recipient: AbstractParty?)
abstract class AbstractRequest(val amount: Amount<Currency>)
}
class CashException(message: String, cause: Throwable) : FlowException(message, cause)

View File

@ -11,6 +11,7 @@ import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
@ -26,6 +27,7 @@ import java.util.*
@StartableByRPC
class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes) : this(amount, issueRef, tracker())
constructor(request: ExitRequest) : this(request.amount, request.issueRef, tracker())
companion object {
fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX)
@ -39,7 +41,7 @@ class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, prog
@Throws(CashException::class)
override fun call(): AbstractCashFlow.Result {
progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionBuilder(notary = null as Party?)
val builder = TransactionBuilder(notary = null as Party?)
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)
val exitStates = Cash.unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
val signers = try {
@ -71,4 +73,7 @@ class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, prog
finaliseTx(participants, tx, "Unable to notarise exit")
return Result(tx, null)
}
@CordaSerializable
class ExitRequest(amount: Amount<Currency>, val issueRef: OpaqueBytes) : AbstractRequest(amount)
}

View File

@ -0,0 +1,49 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import java.util.*
/**
* Initiates a flow that self-issues cash (which should then be sent to recipient(s) using a payment transaction).
*
* We issue cash only to ourselves so that all KYC/AML checks on payments are enforced consistently, rather than risk
* checks for issuance and payments differing. Outside of test scenarios it would be extremely unusual to issue cash
* and immediately transfer it, so impact of this limitation is considered minimal.
*
* @param amount the amount of currency to issue.
* @param issuerBankPartyRef a reference to put on the issued currency.
* @param notary the notary to set on the output states.
*/
@StartableByRPC
class CashIssueAndPaymentFlow(val amount: Amount<Currency>,
val issueRef: OpaqueBytes,
val recipient: Party,
val anonymous: Boolean,
val notary: Party,
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
constructor(amount: Amount<Currency>,
issueRef: OpaqueBytes,
recipient: Party,
anonymous: Boolean,
notary: Party) : this(amount, issueRef, recipient, anonymous, notary, tracker())
constructor(request: IssueAndPaymentRequest) : this(request.amount, request.issueRef, request.recipient, request.anonymous, request.notary, tracker())
@Suspendable
override fun call(): Result {
subFlow(CashIssueFlow(amount, issueRef, notary))
return subFlow(CashPaymentFlow(amount, recipient, anonymous))
}
@CordaSerializable
class IssueAndPaymentRequest(amount: Amount<Currency>,
val issueRef: OpaqueBytes,
val recipient: Party,
val notary: Party,
val anonymous: Boolean) : AbstractRequest(amount)
}

View File

@ -6,9 +6,8 @@ import net.corda.core.contracts.Amount
import net.corda.finance.issuedBy
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.TransactionKeyFlow
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
@ -33,13 +32,14 @@ class CashIssueFlow(val amount: Amount<Currency>,
constructor(amount: Amount<Currency>,
issuerBankPartyRef: OpaqueBytes,
notary: Party) : this(amount, issuerBankPartyRef, notary, tracker())
constructor(request: IssueRequest) : this(request.amount, request.issueRef, request.notary, tracker())
@Suspendable
override fun call(): AbstractCashFlow.Result {
val issuerCert = serviceHub.myInfo.legalIdentityAndCert
progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionBuilder(notary)
val builder = TransactionBuilder(notary)
val issuer = issuerCert.party.ref(issuerBankPartyRef)
val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), issuerCert.party, notary)
progressTracker.currentStep = SIGNING_TX
@ -48,4 +48,7 @@ class CashIssueFlow(val amount: Amount<Currency>,
subFlow(FinalityFlow(tx))
return Result(tx, issuerCert.party)
}
@CordaSerializable
class IssueRequest(amount: Amount<Currency>, val issueRef: OpaqueBytes, val notary: Party) : AbstractRequest(amount)
}

View File

@ -8,6 +8,7 @@ import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.TransactionKeyFlow
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import java.util.*
@ -32,6 +33,7 @@ open class CashPaymentFlow(
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker())
/** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party, anonymous: Boolean) : this(amount, recipient, anonymous, tracker())
constructor(request: PaymentRequest) : this(request.amount, request.recipient, request.anonymous, tracker(), request.issuerConstraint)
@Suspendable
override fun call(): AbstractCashFlow.Result {
@ -41,9 +43,9 @@ open class CashPaymentFlow(
} else {
emptyMap<Party, AnonymousParty>()
}
val anonymousRecipient = txIdentities.get(recipient) ?: recipient
val anonymousRecipient = txIdentities[recipient] ?: recipient
progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionBuilder(null as Party?)
val builder = TransactionBuilder(null as Party?)
// TODO: Have some way of restricting this to states the caller controls
val (spendTX, keysForSigning) = try {
Cash.generateSpend(serviceHub,
@ -62,4 +64,7 @@ open class CashPaymentFlow(
finaliseTx(setOf(recipient), tx, "Unable to notarise spend")
return Result(tx, anonymousRecipient)
}
@CordaSerializable
class PaymentRequest(amount: Amount<Currency>, val recipient: Party, val anonymous: Boolean, val issuerConstraint: Set<Party> = emptySet()) : AbstractRequest(amount)
}

View File

@ -8,16 +8,24 @@ import net.corda.client.mock.pickOne
import net.corda.client.rpc.CordaRPCConnection
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.OpaqueBytes
import net.corda.flows.*
import net.corda.core.utilities.getOrThrow
import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.flows.AbstractCashFlow
import net.corda.flows.CashExitFlow
import net.corda.flows.CashExitFlow.ExitRequest
import net.corda.flows.CashIssueAndPaymentFlow
import net.corda.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
@ -32,24 +40,24 @@ import java.time.Instant
import java.util.*
class ExplorerSimulation(val options: OptionSet) {
val user = User("user1", "test", permissions = setOf(
private val user = User("user1", "test", permissions = setOf(
startFlowPermission<CashPaymentFlow>()
))
val manager = User("manager", "test", permissions = setOf(
private val manager = User("manager", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashExitFlow>())
)
lateinit var notaryNode: NodeHandle
lateinit var aliceNode: NodeHandle
lateinit var bobNode: NodeHandle
lateinit var issuerNodeGBP: NodeHandle
lateinit var issuerNodeUSD: NodeHandle
private lateinit var notaryNode: NodeHandle
private lateinit var aliceNode: NodeHandle
private lateinit var bobNode: NodeHandle
private lateinit var issuerNodeGBP: NodeHandle
private lateinit var issuerNodeUSD: NodeHandle
val RPCConnections = ArrayList<CordaRPCConnection>()
val issuers = HashMap<Currency, CordaRPCOps>()
val parties = ArrayList<Pair<Party, CordaRPCOps>>()
private val RPCConnections = ArrayList<CordaRPCConnection>()
private val issuers = HashMap<Currency, CordaRPCOps>()
private val parties = ArrayList<Pair<Party, CordaRPCOps>>()
init {
startDemoNodes()
@ -141,23 +149,23 @@ class ExplorerSimulation(val options: OptionSet) {
for (i in 0..maxIterations) {
Thread.sleep(300)
// Issuer requests.
eventGenerator.issuerGenerator.map { command ->
when (command) {
is CashFlowCommand.IssueCash -> issuers[command.amount.token]?.let {
println("${Instant.now()} [$i] ISSUING ${command.amount} with ref ${command.issueRef} to ${command.recipient}")
command.startFlow(it).log(i, "${command.amount.token}Issuer")
eventGenerator.issuerGenerator.map { request ->
when (request) {
is IssueAndPaymentRequest -> issuers[request.amount.token]?.let {
println("${Instant.now()} [$i] ISSUING ${request.amount} with ref ${request.issueRef} to ${request.recipient}")
it.startFlow(::CashIssueAndPaymentFlow, request).log(i, "${request.amount.token}Issuer")
}
is CashFlowCommand.ExitCash -> issuers[command.amount.token]?.let {
println("${Instant.now()} [$i] EXITING ${command.amount} with ref ${command.issueRef}")
command.startFlow(it).log(i, "${command.amount.token}Exit")
is ExitRequest -> issuers[request.amount.token]?.let {
println("${Instant.now()} [$i] EXITING ${request.amount} with ref ${request.issueRef}")
it.startFlow(::CashExitFlow, request).log(i, "${request.amount.token}Exit")
}
else -> throw IllegalArgumentException("Unsupported command: $command")
else -> throw IllegalArgumentException("Unsupported command: $request")
}
}.generate(SplittableRandom())
// Party pay requests.
eventGenerator.moveCashGenerator.combine(Generator.pickOne(parties)) { command, (party, rpc) ->
println("${Instant.now()} [$i] SENDING ${command.amount} from $party to ${command.recipient}")
command.startFlow(rpc).log(i, party.name.toString())
eventGenerator.moveCashGenerator.combine(Generator.pickOne(parties)) { request, (party, rpc) ->
println("${Instant.now()} [$i] SENDING ${request.amount} from $party to ${request.recipient}")
rpc.startFlow(::CashPaymentFlow, request).log(i, party.name.toString())
}.generate(SplittableRandom())
}
println("Simulation completed")
@ -177,7 +185,9 @@ class ExplorerSimulation(val options: OptionSet) {
eventGenerator.parties.forEach {
for (ref in 0..1) {
for ((currency, issuer) in issuers) {
CashFlowCommand.IssueCash(Amount(1_000_000, currency), OpaqueBytes(ByteArray(1, { ref.toByte() })), it, notaryNode.nodeInfo.notaryIdentity, anonymous).startFlow(issuer)
val amount = Amount(1_000_000, currency)
issuer.startFlow(::CashIssueFlow, amount, OpaqueBytes(ByteArray(1, { ref.toByte() })), notaryNode.nodeInfo.notaryIdentity).returnValue.getOrThrow()
issuer.startFlow(::CashPaymentFlow, amount, it, anonymous)
}
}
}

View File

@ -36,9 +36,12 @@ import net.corda.explorer.views.bigDecimalFormatter
import net.corda.explorer.views.byteFormatter
import net.corda.explorer.views.stringConverter
import net.corda.flows.AbstractCashFlow
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashExitFlow
import net.corda.flows.CashExitFlow.ExitRequest
import net.corda.flows.CashIssueAndPaymentFlow
import net.corda.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.flows.CashPaymentFlow
import net.corda.flows.CashFlowCommand
import net.corda.flows.CashPaymentFlow.PaymentRequest
import org.controlsfx.dialog.ExceptionDialog
import tornadofx.*
import java.math.BigDecimal
@ -85,8 +88,8 @@ class NewTransaction : Fragment() {
}
})
fun show(window: Window): Unit {
newTransactionDialog(window).showAndWait().ifPresent { command ->
fun show(window: Window) {
newTransactionDialog(window).showAndWait().ifPresent { request ->
val dialog = Alert(Alert.AlertType.INFORMATION).apply {
headerText = null
contentText = "Transaction Started."
@ -94,17 +97,11 @@ class NewTransaction : Fragment() {
initOwner(window)
show()
}
val handle: FlowHandle<AbstractCashFlow.Result> = if (command is CashFlowCommand.IssueCash) {
rpcProxy.value!!.startFlow(::CashIssueFlow,
command.amount,
command.issueRef,
command.notary)
rpcProxy.value!!.startFlow(::CashPaymentFlow,
command.amount,
command.recipient,
command.anonymous)
} else {
command.startFlow(rpcProxy.value!!)
val handle: FlowHandle<AbstractCashFlow.Result> = when (request) {
is IssueAndPaymentRequest -> rpcProxy.value!!.startFlow(::CashIssueAndPaymentFlow, request)
is PaymentRequest -> rpcProxy.value!!.startFlow(::CashPaymentFlow, request)
is ExitRequest -> rpcProxy.value!!.startFlow(::CashExitFlow, request)
else -> throw IllegalArgumentException("Unexpected request type: $request")
}
runAsync {
try {
@ -114,10 +111,11 @@ class NewTransaction : Fragment() {
}
}.ui {
val stx: SignedTransaction = it.stx
val type = when (command) {
is CashFlowCommand.IssueCash -> "Cash Issued"
is CashFlowCommand.ExitCash -> "Cash Exited"
is CashFlowCommand.PayCash -> "Cash Paid"
val type = when (request) {
is IssueAndPaymentRequest -> "Cash Issued"
is ExitRequest -> "Cash Exited"
is PaymentRequest -> "Cash Paid"
else -> throw IllegalArgumentException("Unexpected request type: $request")
}
dialog.alertType = Alert.AlertType.INFORMATION
dialog.dialogPane.content = gridpane {
@ -147,7 +145,7 @@ class NewTransaction : Fragment() {
}
}
private fun newTransactionDialog(window: Window) = Dialog<CashFlowCommand>().apply {
private fun newTransactionDialog(window: Window) = Dialog<AbstractCashFlow.AbstractRequest>().apply {
dialogPane = root
initOwner(window)
setResultConverter {
@ -157,11 +155,9 @@ class NewTransaction : Fragment() {
val issueRef = if (issueRef.value != null) OpaqueBytes.of(issueRef.value) else defaultRef
when (it) {
executeButton -> when (transactionTypeCB.value) {
CashTransaction.Issue -> {
CashFlowCommand.IssueCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity, anonymous)
}
CashTransaction.Pay -> CashFlowCommand.PayCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.legalIdentity, anonymous = anonymous)
CashTransaction.Exit -> CashFlowCommand.ExitCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef)
CashTransaction.Issue -> IssueAndPaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity, anonymous)
CashTransaction.Pay -> PaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.legalIdentity, anonymous = anonymous)
CashTransaction.Exit -> ExitRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef)
else -> null
}
else -> null

View File

@ -2,7 +2,6 @@ package net.corda.loadtest
import com.google.common.util.concurrent.RateLimiter
import net.corda.client.mock.Generator
import net.corda.client.rpc.notUsed
import net.corda.core.crypto.toBase58String
import net.corda.node.services.network.NetworkMapService
import net.corda.testing.driver.PortAllocation

View File

@ -5,12 +5,19 @@ import net.corda.client.mock.pickN
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.finance.USD
import net.corda.core.identity.AbstractParty
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.messaging.startFlow
import net.corda.core.messaging.vaultQueryBy
import net.corda.core.utilities.OpaqueBytes
import net.corda.flows.CashFlowCommand
import net.corda.finance.USD
import net.corda.flows.AbstractCashFlow.AbstractRequest
import net.corda.flows.CashExitFlow
import net.corda.flows.CashExitFlow.ExitRequest
import net.corda.flows.CashIssueAndPaymentFlow
import net.corda.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.flows.CashPaymentFlow
import net.corda.flows.CashPaymentFlow.PaymentRequest
import net.corda.loadtest.LoadTest
import net.corda.loadtest.NodeConnection
import org.slf4j.LoggerFactory
@ -25,20 +32,21 @@ private val log = LoggerFactory.getLogger("CrossCash")
*/
data class CrossCashCommand(
val command: CashFlowCommand,
val request: AbstractRequest,
val node: NodeConnection
) {
override fun toString(): String {
return when (command) {
is CashFlowCommand.IssueCash -> {
"ISSUE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}"
return when (request) {
is IssueAndPaymentRequest -> {
"ISSUE ${node.info.legalIdentity} -> ${request.recipient} : ${request.amount}"
}
is CashFlowCommand.PayCash -> {
"MOVE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}"
is PaymentRequest -> {
"MOVE ${node.info.legalIdentity} -> ${request.recipient} : ${request.amount}"
}
is CashFlowCommand.ExitCash -> {
"EXIT ${node.info.legalIdentity} : ${command.amount}"
is ExitRequest -> {
"EXIT ${node.info.legalIdentity} : ${request.amount}"
}
else -> throw IllegalArgumentException("Unexpected request type: $request")
}
}
}
@ -142,32 +150,31 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
},
interpret = { state, command ->
when (command.command) {
is CashFlowCommand.IssueCash -> {
when (command.request) {
is IssueAndPaymentRequest -> {
val newDiffQueues = state.copyQueues()
val originators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() })
val originators = newDiffQueues.getOrPut(command.request.recipient, { HashMap() })
val issuer = command.node.info.legalIdentity
val quantity = command.command.amount.quantity
val originator = issuer
val queue = originators.getOrPut(originator, { ArrayList() })
val quantity = command.request.amount.quantity
val queue = originators.getOrPut(issuer, { ArrayList() })
queue.add(Pair(issuer, quantity))
CrossCashState(state.nodeVaults, newDiffQueues)
}
is CashFlowCommand.PayCash -> {
is PaymentRequest -> {
val newNodeVaults = state.copyVaults()
val newDiffQueues = state.copyQueues()
val recipientOriginators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() })
val recipientOriginators = newDiffQueues.getOrPut(command.request.recipient, { HashMap() })
val senderQuantities = newNodeVaults[command.node.info.legalIdentity]!!
val amount = command.command.amount
val issuer = command.command.issuerConstraint!!
val amount = command.request.amount
val issuer = command.request.issuerConstraint.single()
val originator = command.node.info.legalIdentity
val senderQuantity = senderQuantities[issuer] ?: throw Exception(
"Generated payment of ${command.command.amount} from ${command.node.info.legalIdentity}, " +
"Generated payment of ${command.request.amount} from ${command.node.info.legalIdentity}, " +
"however there is no cash from $issuer!"
)
if (senderQuantity < amount.quantity) {
throw Exception(
"Generated payment of ${command.command.amount} from ${command.node.info.legalIdentity}, " +
"Generated payment of ${command.request.amount} from ${command.node.info.legalIdentity}, " +
"however they only have $senderQuantity!"
)
}
@ -180,17 +187,17 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
recipientQueue.add(Pair(issuer, amount.quantity))
CrossCashState(newNodeVaults, newDiffQueues)
}
is CashFlowCommand.ExitCash -> {
is ExitRequest -> {
val newNodeVaults = state.copyVaults()
val issuer = command.node.info.legalIdentity
val quantity = command.command.amount.quantity
val quantity = command.request.amount.quantity
val issuerQuantities = newNodeVaults[issuer]!!
val issuerQuantity = issuerQuantities[issuer] ?: throw Exception(
"Generated exit of ${command.command.amount} from $issuer, however there is no cash to exit!"
"Generated exit of ${command.request.amount} from $issuer, however there is no cash to exit!"
)
if (issuerQuantity < quantity) {
throw Exception(
"Generated payment of ${command.command.amount} from $issuer, " +
"Generated payment of ${command.request.amount} from $issuer, " +
"however they only have $issuerQuantity!"
)
}
@ -201,11 +208,18 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
}
CrossCashState(newNodeVaults, state.diffQueues)
}
else -> throw IllegalArgumentException("Unexpected request type: ${command.request}")
}
},
execute = { command ->
val result = command.command.startFlow(command.node.proxy).returnValue
val request = command.request
val result = when (request) {
is IssueAndPaymentRequest -> command.node.proxy.startFlow(::CashIssueAndPaymentFlow, request).returnValue
is PaymentRequest -> command.node.proxy.startFlow(::CashPaymentFlow, request).returnValue
is ExitRequest -> command.node.proxy.startFlow(::CashExitFlow, request).returnValue
else -> throw IllegalArgumentException("Unexpected request type: $request")
}
result.thenMatch({
log.info("Success[$command]: $result")
}, {

View File

@ -8,7 +8,9 @@ import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.withoutIssuer
import net.corda.core.identity.Party
import net.corda.core.utilities.OpaqueBytes
import net.corda.flows.CashFlowCommand
import net.corda.flows.CashExitFlow.ExitRequest
import net.corda.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.flows.CashPaymentFlow.PaymentRequest
import java.util.*
fun generateIssue(
@ -17,12 +19,12 @@ fun generateIssue(
notary: Party,
possibleRecipients: List<Party>,
anonymous: Boolean
): Generator<CashFlowCommand.IssueCash> {
): Generator<IssueAndPaymentRequest> {
return generateAmount(1, max, Generator.pure(currency)).combine(
Generator.pure(OpaqueBytes.of(0)),
Generator.pickOne(possibleRecipients)
) { amount, ref, recipient ->
CashFlowCommand.IssueCash(amount, ref, recipient, notary, anonymous)
IssueAndPaymentRequest(amount, ref, recipient, notary, anonymous)
}
}
@ -32,19 +34,19 @@ fun generateMove(
issuer: Party,
possibleRecipients: List<Party>,
anonymous: Boolean
): Generator<CashFlowCommand.PayCash> {
): Generator<PaymentRequest> {
return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine(
Generator.pickOne(possibleRecipients)
) { amount, recipient ->
CashFlowCommand.PayCash(amount.withoutIssuer(), recipient, issuer, anonymous)
PaymentRequest(amount.withoutIssuer(), recipient, anonymous, setOf(issuer))
}
}
fun generateExit(
max: Long,
currency: Currency
): Generator<CashFlowCommand.ExitCash> {
): Generator<ExitRequest> {
return generateAmount(1, max, Generator.pure(currency)).map { amount ->
CashFlowCommand.ExitCash(amount, OpaqueBytes.of(0))
ExitRequest(amount, OpaqueBytes.of(0))
}
}

View File

@ -5,12 +5,14 @@ import net.corda.client.mock.Generator
import net.corda.client.mock.pickOne
import net.corda.client.mock.replicatePoisson
import net.corda.contracts.asset.Cash
import net.corda.finance.USD
import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty
import net.corda.core.utilities.getOrThrow
import net.corda.core.messaging.startFlow
import net.corda.core.messaging.vaultQueryBy
import net.corda.flows.CashFlowCommand
import net.corda.core.utilities.getOrThrow
import net.corda.finance.USD
import net.corda.flows.CashIssueAndPaymentFlow
import net.corda.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.loadtest.LoadTest
import net.corda.loadtest.NodeConnection
import org.slf4j.LoggerFactory
@ -20,7 +22,7 @@ private val log = LoggerFactory.getLogger("SelfIssue")
// DOCS START 1
data class SelfIssueCommand(
val command: CashFlowCommand.IssueCash,
val request: IssueAndPaymentRequest,
val node: NodeConnection
)
@ -52,16 +54,16 @@ val selfIssueTest = LoadTest<SelfIssueCommand, SelfIssueState>(
}
},
interpret = { state, command ->
interpret = { state, (request, node) ->
val vaults = state.copyVaults()
val issuer = command.node.info.legalIdentity
vaults.put(issuer, (vaults[issuer] ?: 0L) + command.command.amount.quantity)
val issuer = node.info.legalIdentity
vaults.put(issuer, (vaults[issuer] ?: 0L) + request.amount.quantity)
SelfIssueState(vaults)
},
execute = { command ->
execute = { (request, node) ->
try {
val result = command.command.startFlow(command.node.proxy).returnValue.getOrThrow()
val result = node.proxy.startFlow(::CashIssueAndPaymentFlow, request).returnValue.getOrThrow()
log.info("Success: $result")
} catch (e: FlowException) {
log.error("Failure", e)

View File

@ -2,15 +2,22 @@ package net.corda.loadtest.tests
import net.corda.client.mock.Generator
import net.corda.core.contracts.Amount
import net.corda.finance.USD
import net.corda.core.flows.FlowException
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.flows.CashFlowCommand
import net.corda.finance.USD
import net.corda.flows.CashExitFlow
import net.corda.flows.CashExitFlow.ExitRequest
import net.corda.flows.CashIssueAndPaymentFlow
import net.corda.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.flows.CashPaymentFlow
import net.corda.flows.CashPaymentFlow.PaymentRequest
import net.corda.loadtest.LoadTest
object StabilityTest {
private val log = loggerFor<StabilityTest>()
fun crossCashTest(replication: Int) = LoadTest<CrossCashCommand, Unit>(
@ -18,12 +25,18 @@ object StabilityTest {
generate = { _, _ ->
val payments = simpleNodes.flatMap { payer -> simpleNodes.map { payer to it } }
.filter { it.first != it.second }
.map { (payer, payee) -> CrossCashCommand(CashFlowCommand.PayCash(Amount(1, USD), payee.info.legalIdentity, anonymous = true), payer) }
.map { (payer, payee) -> CrossCashCommand(PaymentRequest(Amount(1, USD), payee.info.legalIdentity, anonymous = true), payer) }
Generator.pure(List(replication) { payments }.flatten())
},
interpret = { _, _ -> },
execute = { command ->
val result = command.command.startFlow(command.node.proxy).returnValue
val request = command.request
val result = when (request) {
is IssueAndPaymentRequest -> command.node.proxy.startFlow(::CashIssueAndPaymentFlow, request).returnValue
is PaymentRequest -> command.node.proxy.startFlow(::CashPaymentFlow, request).returnValue
is ExitRequest -> command.node.proxy.startFlow(::CashExitFlow, request).returnValue
else -> throw IllegalArgumentException("Unexpected request type: $request")
}
result.thenMatch({
log.info("Success[$command]: $result")
}, {
@ -39,14 +52,14 @@ object StabilityTest {
// Self issue cash is fast, its ok to flood the node with this command.
val generateIssue =
simpleNodes.map { issuer ->
SelfIssueCommand(CashFlowCommand.IssueCash(Amount(100000, USD), OpaqueBytes.of(0), issuer.info.legalIdentity, notary.info.notaryIdentity, anonymous = true), issuer)
SelfIssueCommand(IssueAndPaymentRequest(Amount(100000, USD), OpaqueBytes.of(0), issuer.info.legalIdentity, notary.info.notaryIdentity, anonymous = true), issuer)
}
Generator.pure(List(replication) { generateIssue }.flatten())
},
interpret = { _, _ -> },
execute = { command ->
execute = { (request, node) ->
try {
val result = command.command.startFlow(command.node.proxy).returnValue.getOrThrow()
val result = node.proxy.startFlow(::CashIssueAndPaymentFlow, request).returnValue.getOrThrow()
log.info("Success: $result")
} catch (e: FlowException) {
log.error("Failure", e)