mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
Flow to issue cash and pay using the returned tx id rather than a vault
query to find the new state
This commit is contained in:
parent
792089e179
commit
0b7678b8ec
@ -0,0 +1,62 @@
|
|||||||
|
package com.r3.corda.enterprise.perftestcordapp.flows
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash
|
||||||
|
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.OnLedgerAsset
|
||||||
|
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PartyAndAmount
|
||||||
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
|
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 issueRef a reference to put on the issued currency.
|
||||||
|
* @param recipient payee Party
|
||||||
|
* @param anonymous whether to anonymise before the transaction
|
||||||
|
* @param notary the notary to set on the output states.
|
||||||
|
*/
|
||||||
|
@StartableByRPC
|
||||||
|
class CashIssueAndPaymentNoSelection(val amount: Amount<Currency>,
|
||||||
|
val issueRef: OpaqueBytes,
|
||||||
|
val recipient: Party,
|
||||||
|
val anonymous: Boolean,
|
||||||
|
val notary: Party,
|
||||||
|
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||||
|
constructor(request: CashIssueAndPaymentFlow.IssueAndPaymentRequest) : this(request.amount, request.issueRef, request.recipient, request.anonymous, request.notary, tracker())
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): Result {
|
||||||
|
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
|
||||||
|
= txState.copy(data = txState.data.copy(amount = amt, owner = owner))
|
||||||
|
|
||||||
|
val issueResult = subFlow(CashIssueFlow(amount, issueRef, notary))
|
||||||
|
val cashStateAndRef = issueResult.stx.tx.outRef<Cash.State>(0)
|
||||||
|
val changeIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
|
||||||
|
val builder = TransactionBuilder(notary)
|
||||||
|
val (spendTx, keysForSigning) = OnLedgerAsset.generateSpend(builder, listOf(PartyAndAmount(recipient, amount)), listOf(cashStateAndRef),
|
||||||
|
changeIdentity.party.anonymise(),
|
||||||
|
{ state, quantity, owner -> deriveState(state, quantity, owner) },
|
||||||
|
{ Cash().generateMoveCommand() })
|
||||||
|
|
||||||
|
progressTracker.currentStep = SIGNING_TX
|
||||||
|
val tx = serviceHub.signInitialTransaction(spendTx, keysForSigning)
|
||||||
|
|
||||||
|
progressTracker.currentStep = FINALISING_TX
|
||||||
|
val notarised = finaliseTx(tx, setOf(recipient), "Unable to notarise spend")
|
||||||
|
return Result(notarised, recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes, payTo: Party, anonymous: Boolean, notary: Party) : this(amount, issueRef, payTo, anonymous, notary, tracker())
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.r3.corda.enterprise.perftestcordapp.flows
|
||||||
|
|
||||||
|
import com.r3.corda.enterprise.perftestcordapp.DOLLARS
|
||||||
|
import com.r3.corda.enterprise.perftestcordapp.`issued by`
|
||||||
|
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.node.services.Vault
|
||||||
|
import net.corda.core.node.services.trackBy
|
||||||
|
import net.corda.core.node.services.vault.QueryCriteria
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.node.internal.StartedNode
|
||||||
|
import net.corda.testing.*
|
||||||
|
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
||||||
|
import net.corda.testing.node.MockNetwork
|
||||||
|
import net.corda.testing.node.MockNetwork.MockNode
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class CashIssueAndPayNoSelectionTests {
|
||||||
|
private lateinit var mockNet: MockNetwork
|
||||||
|
private val initialBalance = 2000.DOLLARS
|
||||||
|
private val ref = OpaqueBytes.of(0x01)
|
||||||
|
private lateinit var bankOfCordaNode: StartedNode<MockNode>
|
||||||
|
private lateinit var bankOfCorda: Party
|
||||||
|
private lateinit var aliceNode: StartedNode<MockNode>
|
||||||
|
private lateinit var notary: Party
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun start() {
|
||||||
|
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset"))
|
||||||
|
bankOfCordaNode = mockNet.createPartyNode(BOC.name)
|
||||||
|
aliceNode = mockNet.createPartyNode(ALICE.name)
|
||||||
|
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
|
||||||
|
mockNet.runNetwork()
|
||||||
|
notary = mockNet.defaultNotaryIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun cleanUp() {
|
||||||
|
mockNet.stopNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `issue and pay some cash`() {
|
||||||
|
val payTo = aliceNode.info.chooseIdentity()
|
||||||
|
val expectedPayment = 500.DOLLARS
|
||||||
|
|
||||||
|
bankOfCordaNode.database.transaction {
|
||||||
|
// Register for vault updates
|
||||||
|
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
|
||||||
|
val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultService.trackBy<Cash.State>(criteria)
|
||||||
|
val (_, vaultUpdatesBankClient) = aliceNode.services.vaultService.trackBy<Cash.State>(criteria)
|
||||||
|
|
||||||
|
val future = bankOfCordaNode.services.startFlow(CashIssueAndPaymentNoSelection(expectedPayment, OpaqueBytes.of(1), payTo, false, notary)).resultFuture
|
||||||
|
mockNet.runNetwork()
|
||||||
|
future.getOrThrow()
|
||||||
|
|
||||||
|
// Check bank of corda vault - should see two consecutive updates of issuing $500
|
||||||
|
// and paying $500 to alice
|
||||||
|
vaultUpdatesBoc.expectEvents {
|
||||||
|
sequence(
|
||||||
|
expect { update ->
|
||||||
|
require(update.produced.size == 1) { "Expected 1 produced states, actual: $update" }
|
||||||
|
val changeState = update.produced.single().state.data
|
||||||
|
assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), changeState.amount)
|
||||||
|
},
|
||||||
|
expect { update ->
|
||||||
|
require(update.consumed.size == 1) { "Expected 1 consumed states, actual: $update" }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check notary node vault updates
|
||||||
|
vaultUpdatesBankClient.expectEvents {
|
||||||
|
expect { (consumed, produced) ->
|
||||||
|
require(consumed.isEmpty()) { consumed.size }
|
||||||
|
require(produced.size == 1) { produced.size }
|
||||||
|
val paymentState = produced.single().state.data
|
||||||
|
assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), paymentState.amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user