mirror of
https://github.com/corda/corda.git
synced 2025-01-20 03:36:29 +00:00
Removing usages of the deprecated CashFlowCommand (#1289)
* Removing usages of the deprecated CashFlowCommand * Addressing review comments
This commit is contained in:
parent
b5d844c4ca
commit
2744079b4b
@ -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(
|
||||
|
@ -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)
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}, {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user