Merge pull request #133 from corda/christians-issue_and_pay

Issue and payment flow without coin selection
This commit is contained in:
Christian Sailer 2017-11-30 09:24:25 +00:00 committed by GitHub
commit 5daa631732
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 259 additions and 0 deletions

View File

@ -0,0 +1,73 @@
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.confidential.SwapIdentitiesFlow
import net.corda.core.contracts.*
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
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())
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes, payTo: Party, anonymous: Boolean, notary: Party) : this(amount, issueRef, payTo, anonymous, 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)
progressTracker.currentStep = GENERATING_ID
val txIdentities = if (anonymous) {
subFlow(SwapIdentitiesFlow(recipient))
} else {
emptyMap<Party, AnonymousParty>()
}
val anonymousRecipient = txIdentities[recipient] ?: recipient
val changeIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
progressTracker.currentStep = GENERATING_TX
val builder = TransactionBuilder(notary)
val (spendTx, keysForSigning) = OnLedgerAsset.generateSpend(builder, listOf(PartyAndAmount(anonymousRecipient, 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)
}
}

View File

@ -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 CashIssueAndPaymentFlowTests {
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(CashIssueAndPaymentFlow(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)
}
}
}
}
}

View File

@ -0,0 +1,99 @@
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 org.junit.runner.RunWith
import org.junit.runners.Parameterized
import kotlin.test.assertEquals
@RunWith(Parameterized::class)
class CashIssueAndPayNoSelectionTests(private val anonymous: Boolean) {
companion object {
@Parameterized.Parameters(name = "Anonymous = {0}")
@JvmStatic
fun data() = listOf(false, true)
}
private lateinit var mockNet: MockNetwork
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, anonymous, 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)
}
}
}
}
}