Change CashIssueFlow to always issue to ourselves

Change CashIssueFlow to always issue to ourselves, and require the cash is then moved in a separate payment
operation. This more closely models actual operation inside banks, and is a step towards making all move-like
operations go through a uniform verification process.
This commit is contained in:
Ross Nicoll 2017-08-11 17:02:39 +01:00
parent 89476904fc
commit b76d036843
29 changed files with 111 additions and 168 deletions

View File

@ -108,14 +108,10 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test @Test
fun `cash issue works end to end`() { fun `cash issue works end to end`() {
val anonymous = false
rpc.startFlow(::CashIssueFlow, rpc.startFlow(::CashIssueFlow,
Amount(100, USD), Amount(100, USD),
aliceNode.legalIdentity,
rpc.nodeIdentity().legalIdentity,
OpaqueBytes(ByteArray(1, { 1 })), OpaqueBytes(ByteArray(1, { 1 })),
notaryNode.notaryIdentity, notaryNode.notaryIdentity
anonymous
) )
vaultUpdates.expectEvents(isStrict = false) { vaultUpdates.expectEvents(isStrict = false) {
@ -137,8 +133,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test @Test
fun `cash issue and move`() { fun `cash issue and move`() {
val anonymous = false val anonymous = false
rpc.startFlow(::CashIssueFlow, 100.DOLLARS, aliceNode.legalIdentity, rpc.nodeIdentity().legalIdentity, OpaqueBytes.of(1), rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), notaryNode.notaryIdentity).returnValue.getOrThrow()
notaryNode.notaryIdentity, anonymous).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity, anonymous).returnValue.getOrThrow() rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity, anonymous).returnValue.getOrThrow()
var issueSmId: StateMachineRunId? = null var issueSmId: StateMachineRunId? = null

View File

@ -71,7 +71,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class, FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class,
DOLLARS(123), OpaqueBytes.of("1".getBytes()), DOLLARS(123), OpaqueBytes.of("1".getBytes()),
node.info.getLegalIdentity(), node.info.getLegalIdentity()); node.info.getLegalIdentity());
System.out.println("Started issuing cash, waiting on result"); System.out.println("Started issuing cash, waiting on result");
flowHandle.getReturnValue().get(); flowHandle.getReturnValue().get();

View File

@ -78,8 +78,7 @@ class CordaRPCClientTest : NodeBasedTest() {
println("Creating proxy") println("Creating proxy")
println("Starting flow") println("Starting flow")
val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow, val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow,
20.DOLLARS, node.info.legalIdentity, 20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity
node.info.legalIdentity, OpaqueBytes.of(0), node.info.legalIdentity, true
) )
println("Started flow, waiting on result") println("Started flow, waiting on result")
flowHandle.progress.subscribe { flowHandle.progress.subscribe {
@ -114,8 +113,7 @@ class CordaRPCClientTest : NodeBasedTest() {
assertTrue(startCash.isEmpty(), "Should not start with any cash") assertTrue(startCash.isEmpty(), "Should not start with any cash")
val flowHandle = proxy.startFlow(::CashIssueFlow, val flowHandle = proxy.startFlow(::CashIssueFlow,
123.DOLLARS, node.info.legalIdentity, 123.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity
node.info.legalIdentity, OpaqueBytes.of(0), node.info.legalIdentity, true
) )
println("Started issuing cash, waiting on result") println("Started issuing cash, waiting on result")
flowHandle.returnValue.get() flowHandle.returnValue.get()
@ -141,15 +139,16 @@ class CordaRPCClientTest : NodeBasedTest() {
} }
} }
val nodeIdentity = node.info.legalIdentity val nodeIdentity = node.info.legalIdentity
node.services.startFlow(CashIssueFlow(2000.DOLLARS, nodeIdentity, nodeIdentity, OpaqueBytes.of(0), nodeIdentity, true), FlowInitiator.Shell).resultFuture.getOrThrow() node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).resultFuture.getOrThrow()
proxy.startFlow(::CashIssueFlow, proxy.startFlow(::CashIssueFlow,
123.DOLLARS, nodeIdentity, 123.DOLLARS,
nodeIdentity, OpaqueBytes.of(0), nodeIdentity, OpaqueBytes.of(0),
true nodeIdentity
).returnValue.getOrThrow() ).returnValue.getOrThrow()
proxy.startFlowDynamic(CashIssueFlow::class.java, proxy.startFlowDynamic(CashIssueFlow::class.java,
1000.DOLLARS, OpaqueBytes.of(0), 1000.DOLLARS,
nodeIdentity, nodeIdentity).returnValue.getOrThrow() OpaqueBytes.of(0),
nodeIdentity).returnValue.getOrThrow()
assertEquals(2, countRpcFlows) assertEquals(2, countRpcFlows)
assertEquals(1, countShellFlows) assertEquals(1, countShellFlows)
} }

View File

@ -75,7 +75,7 @@ public class StandaloneCordaRPCJavaClientTest {
FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class, FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class,
dollars123, OpaqueBytes.of("1".getBytes()), dollars123, OpaqueBytes.of("1".getBytes()),
notaryNode.getLegalIdentity(), notaryNode.getLegalIdentity()); notaryNode.getLegalIdentity());
System.out.println("Started issuing cash, waiting on result"); System.out.println("Started issuing cash, waiting on result");
flowHandle.getReturnValue().get(); flowHandle.getReturnValue().get();

View File

@ -3,7 +3,6 @@ package net.corda.kotlin.rpc
import com.google.common.hash.Hashing import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream import com.google.common.hash.HashingInputStream
import net.corda.client.rpc.CordaRPCConnection import net.corda.client.rpc.CordaRPCConnection
import net.corda.client.rpc.notUsed
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.getCashBalance import net.corda.contracts.getCashBalance
import net.corda.contracts.getCashBalances import net.corda.contracts.getCashBalances
@ -96,7 +95,7 @@ class StandaloneCordaRPClientTest {
@Test @Test
fun `test starting flow`() { fun `test starting flow`() {
rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity) rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout) .returnValue.getOrThrow(timeout)
} }
@ -104,7 +103,7 @@ class StandaloneCordaRPClientTest {
fun `test starting tracked flow`() { fun `test starting tracked flow`() {
var trackCount = 0 var trackCount = 0
val handle = rpcProxy.startTrackedFlow( val handle = rpcProxy.startTrackedFlow(
::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity ::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryNode.notaryIdentity
) )
handle.progress.subscribe { msg -> handle.progress.subscribe { msg ->
log.info("Flow>> $msg") log.info("Flow>> $msg")
@ -133,7 +132,7 @@ class StandaloneCordaRPClientTest {
} }
// Now issue some cash // Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity) rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout) .returnValue.getOrThrow(timeout)
assertEquals(1, updateCount.get()) assertEquals(1, updateCount.get())
} }
@ -150,7 +149,7 @@ class StandaloneCordaRPClientTest {
} }
// Now issue some cash // Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity) rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout) .returnValue.getOrThrow(timeout)
assertNotEquals(0, updateCount.get()) assertNotEquals(0, updateCount.get())
@ -164,7 +163,7 @@ class StandaloneCordaRPClientTest {
@Test @Test
fun `test vault query by`() { fun `test vault query by`() {
// Now issue some cash // Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity) rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout) .returnValue.getOrThrow(timeout)
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
@ -192,10 +191,7 @@ class StandaloneCordaRPClientTest {
val startCash = rpcProxy.getCashBalances() val startCash = rpcProxy.getCashBalances()
assertTrue(startCash.isEmpty(), "Should not start with any cash") assertTrue(startCash.isEmpty(), "Should not start with any cash")
val flowHandle = rpcProxy.startFlow(::CashIssueFlow, val flowHandle = rpcProxy.startFlow(::CashIssueFlow, 629.DOLLARS, OpaqueBytes.of(0), notaryNode.legalIdentity)
629.DOLLARS, OpaqueBytes.of(0),
notaryNode.legalIdentity, notaryNode.legalIdentity
)
println("Started issuing cash, waiting on result") println("Started issuing cash, waiting on result")
flowHandle.returnValue.get() flowHandle.returnValue.get()

View File

@ -175,8 +175,7 @@ class ContractUpgradeFlowTest {
@Test @Test
fun `upgrade Cash to v2`() { fun `upgrade Cash to v2`() {
// Create some cash. // Create some cash.
val anonymous = false val result = a.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)).resultFuture
val result = a.services.startFlow(CashIssueFlow(Amount(1000, USD), a.info.legalIdentity, a.info.legalIdentity, OpaqueBytes.of(1), notary, anonymous)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
val stx = result.getOrThrow().stx val stx = result.getOrThrow().stx
val stateAndRef = stx.tx.outRef<Cash.State>(0) val stateAndRef = stx.tx.outRef<Cash.State>(0)

View File

@ -1,8 +1,6 @@
package net.corda.docs package net.corda.docs
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.core.concurrent.CordaFuture
import net.corda.finance.DOLLARS
import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.messaging.vaultTrackBy import net.corda.core.messaging.vaultTrackBy
@ -10,6 +8,7 @@ import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow import net.corda.flows.CashPaymentFlow
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
@ -17,13 +16,7 @@ import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.testing.parallel
import net.corda.testing.sequence
import org.junit.Test import org.junit.Test
import java.util.*
import kotlin.concurrent.thread
import kotlin.test.assertEquals import kotlin.test.assertEquals
class IntegrationTestingTutorial { class IntegrationTestingTutorial {
@ -32,7 +25,8 @@ class IntegrationTestingTutorial {
// START 1 // START 1
driver { driver {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlowPermission<CashIssueFlow>() startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
)) ))
val bobUser = User("bobUser", "testPassword2", permissions = setOf( val bobUser = User("bobUser", "testPassword2", permissions = setOf(
startFlowPermission<CashPaymentFlow>() startFlowPermission<CashPaymentFlow>()
@ -62,20 +56,21 @@ class IntegrationTestingTutorial {
// START 4 // START 4
val issueRef = OpaqueBytes.of(0) val issueRef = OpaqueBytes.of(0)
val futures = Stack<CordaFuture<*>>()
(1..10).map { i -> (1..10).map { i ->
thread { aliceProxy.startFlow(::CashIssueFlow,
futures.push(aliceProxy.startFlow(::CashIssueFlow, i.DOLLARS,
issueRef,
notary.nodeInfo.notaryIdentity
).returnValue
}.transpose().getOrThrow()
// We wait for all of the issuances to run before we start making payments
(1..10).map { i ->
aliceProxy.startFlow(::CashPaymentFlow,
i.DOLLARS, i.DOLLARS,
bob.nodeInfo.legalIdentity, bob.nodeInfo.legalIdentity,
alice.nodeInfo.legalIdentity,
issueRef,
notary.nodeInfo.notaryIdentity,
true true
).returnValue) ).returnValue
} }.transpose().getOrThrow()
}.forEach(Thread::join) // Ensure the stack of futures is populated.
futures.forEach { it.getOrThrow() }
bobVaultUpdates.expectEvents { bobVaultUpdates.expectEvents {
parallel( parallel(

View File

@ -128,7 +128,7 @@ fun generateTransactions(proxy: CordaRPCOps) {
proxy.startFlow(::CashPaymentFlow, Amount(quantity, USD), me) proxy.startFlow(::CashPaymentFlow, Amount(quantity, USD), me)
} else { } else {
val quantity = Math.abs(random.nextLong() % 1000) val quantity = Math.abs(random.nextLong() % 1000)
proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), me, me, issueRef, notary, true) proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, notary)
ownedQuantity += quantity ownedQuantity += quantity
} }
} }

View File

@ -133,8 +133,7 @@ object TopupIssuerFlow {
// invoke Cash subflow to issue Asset // invoke Cash subflow to issue Asset
progressTracker.currentStep = ISSUING progressTracker.currentStep = ISSUING
val issuer = serviceHub.myInfo.legalIdentity val issuer = serviceHub.myInfo.legalIdentity
val issueRecipient = serviceHub.myInfo.legalIdentity val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, notaryParty)
val issueCashFlow = CashIssueFlow(amount, issueRecipient, issuer, issuerPartyRef, notaryParty, anonymous = false)
val issueTx = subFlow(issueCashFlow) val issueTx = subFlow(issueCashFlow)
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger) // NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
// short-circuit when issuing to self // short-circuit when issuing to self

View File

@ -65,11 +65,8 @@ class CustomVaultQueryTest {
private fun issueCashForCurrency(amountToIssue: Amount<Currency>) { private fun issueCashForCurrency(amountToIssue: Amount<Currency>) {
// Use NodeA as issuer and create some dollars // Use NodeA as issuer and create some dollars
val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(amountToIssue, val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(amountToIssue,
nodeA.info.legalIdentity,
nodeA.info.legalIdentity,
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
notaryNode.info.notaryIdentity, notaryNode.info.notaryIdentity))
anonymous = false))
// Wait for the flow to stop and print // Wait for the flow to stop and print
flowHandle1.resultFuture.getOrThrow() flowHandle1.resultFuture.getOrThrow()
} }

View File

@ -45,22 +45,16 @@ class FxTransactionBuildTutorialTest {
fun `Run ForeignExchangeFlow to completion`() { fun `Run ForeignExchangeFlow to completion`() {
// Use NodeA as issuer and create some dollars // Use NodeA as issuer and create some dollars
val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(DOLLARS(1000), val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(DOLLARS(1000),
nodeA.info.legalIdentity,
nodeA.info.legalIdentity,
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
notaryNode.info.notaryIdentity, notaryNode.info.notaryIdentity))
false))
// Wait for the flow to stop and print // Wait for the flow to stop and print
flowHandle1.resultFuture.getOrThrow() flowHandle1.resultFuture.getOrThrow()
printBalances() printBalances()
// Using NodeB as Issuer create some pounds. // Using NodeB as Issuer create some pounds.
val flowHandle2 = nodeB.services.startFlow(CashIssueFlow(POUNDS(1000), val flowHandle2 = nodeB.services.startFlow(CashIssueFlow(POUNDS(1000),
nodeB.info.legalIdentity,
nodeB.info.legalIdentity,
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
notaryNode.info.notaryIdentity, notaryNode.info.notaryIdentity))
false))
// Wait for flow to come to an end and print // Wait for flow to come to an end and print
flowHandle2.resultFuture.getOrThrow() flowHandle2.resultFuture.getOrThrow()
printBalances() printBalances()

View File

@ -6,11 +6,13 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import java.util.* import java.util.*
/** /**
* A command to initiate the cash flow with. * A command to initiate the cash flow with.
*/ */
@Deprecated("Please use the flows directly, these will be removed in a later release")
sealed class CashFlowCommand { sealed class CashFlowCommand {
abstract fun startFlow(proxy: CordaRPCOps): FlowHandle<AbstractCashFlow.Result> abstract fun startFlow(proxy: CordaRPCOps): FlowHandle<AbstractCashFlow.Result>
@ -22,7 +24,10 @@ sealed class CashFlowCommand {
val recipient: Party, val recipient: Party,
val notary: Party, val notary: Party,
val anonymous: Boolean) : CashFlowCommand() { val anonymous: Boolean) : CashFlowCommand() {
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashIssueFlow, amount, recipient, proxy.nodeIdentity().legalIdentity, issueRef, notary, anonymous) override fun startFlow(proxy: CordaRPCOps): FlowHandle<AbstractCashFlow.Result> {
proxy.startFlow(::CashIssueFlow, amount, issueRef, notary).returnValue.getOrThrow()
return proxy.startFlow(::CashPaymentFlow, amount, recipient, anonymous)
}
} }
/** /**

View File

@ -15,51 +15,37 @@ import net.corda.core.utilities.ProgressTracker
import java.util.* import java.util.*
/** /**
* Initiates a flow that produces cash issuance transaction. * 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 amount the amount of currency to issue.
* @param issuerBankPartyRef a reference to put on the issued currency. * @param issuerBankPartyRef a reference to put on the issued currency.
* @param issueTo the party who should own the currency after it is issued.
* @param notary the notary to set on the output states. * @param notary the notary to set on the output states.
*/ */
@StartableByRPC @StartableByRPC
class CashIssueFlow(val amount: Amount<Currency>, class CashIssueFlow(val amount: Amount<Currency>,
val issueTo: Party,
val issuerBankParty
: Party,
val issuerBankPartyRef: OpaqueBytes, val issuerBankPartyRef: OpaqueBytes,
val notary: Party, val notary: Party,
val anonymous: Boolean,
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) { progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
constructor(amount: Amount<Currency>, constructor(amount: Amount<Currency>,
issuerBankPartyRef: OpaqueBytes, issuerBankPartyRef: OpaqueBytes,
issuerBankParty: Party, notary: Party) : this(amount, issuerBankPartyRef, notary, tracker())
issueTo: Party,
notary: Party) : this(amount, issueTo, issuerBankParty, issuerBankPartyRef, notary, true, tracker())
constructor(amount: Amount<Currency>,
issueTo: Party,
issuerBankParty: Party,
issuerBankPartyRef: OpaqueBytes,
notary: Party,
anonymous: Boolean) : this(amount, issueTo, issuerBankParty, issuerBankPartyRef, notary, anonymous, tracker())
@Suspendable @Suspendable
override fun call(): AbstractCashFlow.Result { override fun call(): AbstractCashFlow.Result {
progressTracker.currentStep = GENERATING_ID val issuerCert = serviceHub.myInfo.legalIdentityAndCert
val txIdentities = if (anonymous) {
subFlow(TransactionKeyFlow(issueTo))
} else {
emptyMap<Party, AnonymousParty>()
}
val anonymousRecipient = txIdentities[issueTo] ?: issueTo
progressTracker.currentStep = GENERATING_TX progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionBuilder(notary) val builder: TransactionBuilder = TransactionBuilder(notary)
val issuer = issuerBankParty.ref(issuerBankPartyRef) val issuer = issuerCert.party.ref(issuerBankPartyRef)
val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), anonymousRecipient, notary) val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), issuerCert.party, notary)
progressTracker.currentStep = SIGNING_TX progressTracker.currentStep = SIGNING_TX
val tx = serviceHub.signInitialTransaction(builder, signers) val tx = serviceHub.signInitialTransaction(builder, signers)
progressTracker.currentStep = FINALISING_TX progressTracker.currentStep = FINALISING_TX
subFlow(FinalityFlow(tx)) subFlow(FinalityFlow(tx))
return Result(tx, anonymousRecipient) return Result(tx, issuerCert.party)
} }
} }

View File

@ -33,11 +33,7 @@ class CashExitFlowTests {
bankOfCorda = bankOfCordaNode.info.legalIdentity bankOfCorda = bankOfCordaNode.info.legalIdentity
mockNet.runNetwork() mockNet.runNetwork()
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture
bankOfCorda,
bankOfCorda, ref,
notary,
anonymous = true)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() future.getOrThrow()
} }
@ -50,8 +46,7 @@ class CashExitFlowTests {
@Test @Test
fun `exit some cash`() { fun `exit some cash`() {
val exitAmount = 500.DOLLARS val exitAmount = 500.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, ref)).resultFuture
ref)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
val exitTx = future.getOrThrow().stx.tx val exitTx = future.getOrThrow().stx.tx
val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref)) val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref))
@ -64,8 +59,7 @@ class CashExitFlowTests {
@Test @Test
fun `exit zero cash`() { fun `exit zero cash`() {
val expected = 0.DOLLARS val expected = 0.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, ref)).resultFuture
ref)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<CashException> { assertFailsWith<CashException> {
future.getOrThrow() future.getOrThrow()

View File

@ -42,12 +42,7 @@ class CashIssueFlowTests {
fun `issue some cash`() { fun `issue some cash`() {
val expected = 500.DOLLARS val expected = 500.DOLLARS
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture
bankOfCorda,
bankOfCorda,
ref,
notary,
anonymous = true)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
val issueTx = future.getOrThrow().stx val issueTx = future.getOrThrow().stx
val output = issueTx.tx.outputsOfType<Cash.State>().single() val output = issueTx.tx.outputsOfType<Cash.State>().single()
@ -58,12 +53,7 @@ class CashIssueFlowTests {
fun `issue zero cash`() { fun `issue zero cash`() {
val expected = 0.DOLLARS val expected = 0.DOLLARS
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture
bankOfCorda,
bankOfCorda,
ref,
notary,
anonymous = true)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
future.getOrThrow() future.getOrThrow()

View File

@ -37,12 +37,7 @@ class CashPaymentFlowTests {
notary = notaryNode.info.notaryIdentity notary = notaryNode.info.notaryIdentity
bankOfCorda = bankOfCordaNode.info.legalIdentity bankOfCorda = bankOfCordaNode.info.legalIdentity
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture
bankOfCorda,
bankOfCorda,
ref,
notary,
true)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() future.getOrThrow()
} }

View File

@ -111,7 +111,7 @@ class NodePerformanceTests {
a.rpcClientToNode().use("A", "A") { connection -> a.rpcClientToNode().use("A", "A") { connection ->
println("ISSUING") println("ISSUING")
val doneFutures = (1..100).toList().parallelStream().map { val doneFutures = (1..100).toList().parallelStream().map {
connection.proxy.startFlow(::CashIssueFlow, 1.DOLLARS, OpaqueBytes.of(0), a.nodeInfo.legalIdentity, a.nodeInfo.notaryIdentity).returnValue connection.proxy.startFlow(::CashIssueFlow, 1.DOLLARS, OpaqueBytes.of(0), a.nodeInfo.notaryIdentity).returnValue
}.toList() }.toList()
doneFutures.transpose().get() doneFutures.transpose().get()
println("STARTING PAYMENT") println("STARTING PAYMENT")

View File

@ -134,14 +134,10 @@ class DistributedServiceTests : DriverBasedTest() {
} }
private fun issueCash(amount: Amount<Currency>) { private fun issueCash(amount: Amount<Currency>) {
val issueHandle = aliceProxy.startFlow( aliceProxy.startFlow(::CashIssueFlow, amount, OpaqueBytes.of(0), raftNotaryIdentity).returnValue.getOrThrow()
::CashIssueFlow,
amount, OpaqueBytes.of(0), alice.nodeInfo.legalIdentity, raftNotaryIdentity)
issueHandle.returnValue.getOrThrow()
} }
private fun paySelf(amount: Amount<Currency>) { private fun paySelf(amount: Amount<Currency>) {
val payHandle = aliceProxy.startFlow(::CashPaymentFlow, amount, alice.nodeInfo.legalIdentity) aliceProxy.startFlow(::CashPaymentFlow, amount, alice.nodeInfo.legalIdentity).returnValue.getOrThrow()
payHandle.returnValue.getOrThrow()
} }
} }

View File

@ -93,9 +93,8 @@ class CordaRPCOpsImplTest {
} }
// Tell the monitoring service node to issue some cash // Tell the monitoring service node to issue some cash
val anonymous = false
val recipient = aliceNode.info.legalIdentity val recipient = aliceNode.info.legalIdentity
val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), recipient, rpc.nodeIdentity().legalIdentity, ref, notaryNode.info.notaryIdentity, anonymous) val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, notaryNode.info.notaryIdentity)
mockNet.runNetwork() mockNet.runNetwork()
var issueSmId: StateMachineRunId? = null var issueSmId: StateMachineRunId? = null
@ -133,11 +132,8 @@ class CordaRPCOpsImplTest {
val anonymous = false val anonymous = false
val result = rpc.startFlow(::CashIssueFlow, val result = rpc.startFlow(::CashIssueFlow,
100.DOLLARS, 100.DOLLARS,
aliceNode.info.legalIdentity,
rpc.nodeIdentity().legalIdentity,
OpaqueBytes(ByteArray(1, { 1 })), OpaqueBytes(ByteArray(1, { 1 })),
notaryNode.info.notaryIdentity, notaryNode.info.notaryIdentity
false
) )
mockNet.runNetwork() mockNet.runNetwork()
@ -212,14 +208,7 @@ class CordaRPCOpsImplTest {
fun `cash command by user not permissioned for cash`() { fun `cash command by user not permissioned for cash`() {
CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = emptySet()))) CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = emptySet())))
assertThatExceptionOfType(PermissionException::class.java).isThrownBy { assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
rpc.startFlow(::CashIssueFlow, rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), notaryNode.info.notaryIdentity)
Amount(100, USD),
aliceNode.info.legalIdentity,
rpc.nodeIdentity().legalIdentity,
OpaqueBytes(ByteArray(1, { 1 })),
notaryNode.info.notaryIdentity,
false
)
} }
} }

View File

@ -330,11 +330,8 @@ class FlowFrameworkTests {
assertEquals(notary1.info.notaryIdentity, notary2.info.notaryIdentity) assertEquals(notary1.info.notaryIdentity, notary2.info.notaryIdentity)
node1.services.startFlow(CashIssueFlow( node1.services.startFlow(CashIssueFlow(
2000.DOLLARS, 2000.DOLLARS,
node1.info.legalIdentity,
node1.info.legalIdentity,
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
notary1.info.notaryIdentity, notary1.info.notaryIdentity))
anonymous = false))
// We pay a couple of times, the notary picking should go round robin // We pay a couple of times, the notary picking should go round robin
for (i in 1..3) { for (i in 1..3) {
val flow = node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity, anonymous = false)) val flow = node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity, anonymous = false))

View File

@ -9,6 +9,7 @@ import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
@ -20,7 +21,9 @@ class BankOfCordaRPCClientTest {
@Test @Test
fun `issuer flow via RPC`() { fun `issuer flow via RPC`() {
driver(dsl = { driver(dsl = {
val bocManager = User("bocManager", "password1", permissions = setOf(startFlowPermission<CashIssueFlow>())) val bocManager = User("bocManager", "password1", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()))
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet()) val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet())
val (nodeBankOfCorda, nodeBigCorporation) = listOf( val (nodeBankOfCorda, nodeBigCorporation) = listOf(
startNode(BOC.name, setOf(ServiceInfo(SimpleNotaryService.type)), listOf(bocManager)), startNode(BOC.name, setOf(ServiceInfo(SimpleNotaryService.type)), listOf(bocManager)),
@ -47,10 +50,12 @@ class BankOfCordaRPCClientTest {
bocProxy.startFlow( bocProxy.startFlow(
::CashIssueFlow, ::CashIssueFlow,
1000.DOLLARS, 1000.DOLLARS,
nodeBigCorporation.nodeInfo.legalIdentity,
nodeBankOfCorda.nodeInfo.legalIdentity,
BIG_CORP_PARTY_REF, BIG_CORP_PARTY_REF,
nodeBankOfCorda.nodeInfo.notaryIdentity, nodeBankOfCorda.nodeInfo.notaryIdentity).returnValue.getOrThrow()
bocProxy.startFlow(
::CashPaymentFlow,
1000.DOLLARS,
nodeBigCorporation.nodeInfo.legalIdentity,
anonymous).returnValue.getOrThrow() anonymous).returnValue.getOrThrow()
// Check Bank of Corda Vault Updates // Check Bank of Corda Vault Updates

View File

@ -70,7 +70,9 @@ private class BankOfCordaDriver {
startFlowPermission<CashPaymentFlow>(), startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashIssueFlow>(), startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashExitFlow>())) startFlowPermission<CashExitFlow>()))
val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf(startFlowPermission<CashPaymentFlow>())) val bigCorpUser = User(BIGCORP_USERNAME, "test",
permissions = setOf(
startFlowPermission<CashPaymentFlow>()))
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type))) startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
val bankOfCorda = startNode( val bankOfCorda = startNode(
BOC.name, BOC.name,

View File

@ -9,6 +9,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.testing.http.HttpApi import net.corda.testing.http.HttpApi
import java.util.* import java.util.*
@ -28,6 +29,8 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
/** /**
* RPC API * RPC API
*
* @return a pair of the issuing and payment transactions.
*/ */
fun requestRPCIssue(params: IssueRequestParams): SignedTransaction { fun requestRPCIssue(params: IssueRequestParams): SignedTransaction {
val client = CordaRPCClient(hostAndPort) val client = CordaRPCClient(hostAndPort)
@ -38,8 +41,6 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
// Resolve parties via RPC // Resolve parties via RPC
val issueToParty = rpc.partyFromX500Name(params.issueToPartyName) val issueToParty = rpc.partyFromX500Name(params.issueToPartyName)
?: throw Exception("Unable to locate ${params.issueToPartyName} in Network Map Service") ?: throw Exception("Unable to locate ${params.issueToPartyName} in Network Map Service")
val issuerBankParty = rpc.partyFromX500Name(params.issuerBankName)
?: throw Exception("Unable to locate ${params.issuerBankName} in Network Map Service")
val notaryLegalIdentity = rpc.partyFromX500Name(params.notaryName) val notaryLegalIdentity = rpc.partyFromX500Name(params.notaryName)
?: throw IllegalStateException("Unable to locate ${params.notaryName} in Network Map Service") ?: throw IllegalStateException("Unable to locate ${params.notaryName} in Network Map Service")
val notaryNode = rpc.nodeIdentityFromParty(notaryLegalIdentity) val notaryNode = rpc.nodeIdentityFromParty(notaryLegalIdentity)
@ -48,7 +49,9 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
val amount = Amount(params.amount, Currency.getInstance(params.currency)) val amount = Amount(params.amount, Currency.getInstance(params.currency))
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte()) val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
return rpc.startFlow(::CashIssueFlow, amount, issueToParty, issuerBankParty, issuerBankPartyRef, notaryNode.notaryIdentity, params.anonymous) rpc.startFlow(::CashIssueFlow, amount, issuerBankPartyRef, notaryNode.notaryIdentity)
.returnValue.getOrThrow().stx
return rpc.startFlow(::CashPaymentFlow, amount, issueToParty, params.anonymous)
.returnValue.getOrThrow().stx .returnValue.getOrThrow().stx
} }
} }

View File

@ -7,6 +7,7 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
@ -53,12 +54,13 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
val amount = Amount(params.amount, Currency.getInstance(params.currency)) val amount = Amount(params.amount, Currency.getInstance(params.currency))
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte()) val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
val anonymous = params.anonymous
// invoke client side of Issuer Flow: IssuanceRequester // invoke client side of Issuer Flow: IssuanceRequester
// The line below blocks and waits for the future to resolve. // The line below blocks and waits for the future to resolve.
return try { return try {
rpc.startFlow(::CashIssueFlow, amount, issueToParty, issuerBankParty, issuerBankPartyRef, notaryNode.notaryIdentity, anonymous).returnValue.getOrThrow() rpc.startFlow(::CashIssueFlow, amount, issuerBankPartyRef, notaryNode.notaryIdentity).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow, amount, issueToParty, params.anonymous)
.returnValue.getOrThrow().stx
logger.info("Issue request completed successfully: $params") logger.info("Issue request completed successfully: $params")
Response.status(Response.Status.CREATED).build() Response.status(Response.Status.CREATED).build()
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -7,6 +7,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
@ -27,7 +28,9 @@ class TraderDemoTest : NodeBasedTest() {
@Test @Test
fun `runs trader demo`() { fun `runs trader demo`() {
val demoUser = User("demo", "demo", setOf(startFlowPermission<SellerFlow>())) val demoUser = User("demo", "demo", setOf(startFlowPermission<SellerFlow>()))
val bankUser = User("user1", "test", permissions = setOf(startFlowPermission<CashIssueFlow>(), val bankUser = User("user1", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CommercialPaperIssueFlow>())) startFlowPermission<CommercialPaperIssueFlow>()))
val (nodeA, nodeB, bankNode) = listOf( val (nodeA, nodeB, bankNode) = listOf(
startNode(DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)), startNode(DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),

View File

@ -16,6 +16,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.vault.VaultSchemaV1
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.contracts.calculateRandomlySizedAmounts import net.corda.testing.contracts.calculateRandomlySizedAmounts
@ -52,10 +53,10 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
?: throw IllegalStateException("Unable to locate notary node in network map cache") ?: throw IllegalStateException("Unable to locate notary node in network map cache")
val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random()) val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random())
val anonymous = false val anonymous = false
// issue random amounts of currency up to the requested amount, in parallel rpc.startFlow(::CashIssueFlow, amount, OpaqueBytes.of(1), notaryNode.notaryIdentity).returnValue.getOrThrow()
// pay random amounts of currency up to the requested amount, in parallel
val resultFutures = amounts.map { pennies -> val resultFutures = amounts.map { pennies ->
rpc.startFlow(::CashIssueFlow, amount.copy(quantity = pennies), buyer, rpc.nodeIdentity().legalIdentity, rpc.startFlow(::CashPaymentFlow, amount.copy(quantity = pennies), buyer, anonymous).returnValue
OpaqueBytes.of(1), notaryNode.notaryIdentity, anonymous).returnValue
} }
resultFutures.transpose().getOrThrow() resultFutures.transpose().getOrThrow()

View File

@ -38,8 +38,7 @@ class ExplorerSimulation(val options: OptionSet) {
val manager = User("manager", "test", permissions = setOf( val manager = User("manager", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(), startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>(), startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashExitFlow>(), startFlowPermission<CashExitFlow>())
startFlowPermission<IssuerFlow.IssuanceRequester>())
) )
lateinit var notaryNode: NodeHandle lateinit var notaryNode: NodeHandle

View File

@ -36,8 +36,9 @@ import net.corda.explorer.views.bigDecimalFormatter
import net.corda.explorer.views.byteFormatter import net.corda.explorer.views.byteFormatter
import net.corda.explorer.views.stringConverter import net.corda.explorer.views.stringConverter
import net.corda.flows.AbstractCashFlow import net.corda.flows.AbstractCashFlow
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.flows.CashFlowCommand import net.corda.flows.CashFlowCommand
import net.corda.flows.IssuerFlow.IssuanceRequester
import org.controlsfx.dialog.ExceptionDialog import org.controlsfx.dialog.ExceptionDialog
import tornadofx.* import tornadofx.*
import java.math.BigDecimal import java.math.BigDecimal
@ -94,12 +95,13 @@ class NewTransaction : Fragment() {
show() show()
} }
val handle: FlowHandle<AbstractCashFlow.Result> = if (command is CashFlowCommand.IssueCash) { val handle: FlowHandle<AbstractCashFlow.Result> = if (command is CashFlowCommand.IssueCash) {
rpcProxy.value!!.startFlow(::IssuanceRequester, rpcProxy.value!!.startFlow(::CashIssueFlow,
command.amount,
command.issueRef,
command.notary)
rpcProxy.value!!.startFlow(::CashPaymentFlow,
command.amount, command.amount,
command.recipient, command.recipient,
command.issueRef,
myIdentity.value!!.legalIdentity,
command.notary,
command.anonymous) command.anonymous)
} else { } else {
command.startFlow(rpcProxy.value!!) command.startFlow(rpcProxy.value!!)

View File

@ -118,7 +118,7 @@ class VerifierTests {
val alice = aliceFuture.get() val alice = aliceFuture.get()
val notary = notaryFuture.get() val notary = notaryFuture.get()
startVerifier(notary) startVerifier(notary)
alice.rpc.startFlow(::CashIssueFlow, 10.DOLLARS, OpaqueBytes.of(0), alice.nodeInfo.legalIdentity, notaryFuture.get().nodeInfo.notaryIdentity).returnValue.get() alice.rpc.startFlow(::CashIssueFlow, 10.DOLLARS, OpaqueBytes.of(0), notaryFuture.get().nodeInfo.notaryIdentity).returnValue.get()
notary.waitUntilNumberOfVerifiers(1) notary.waitUntilNumberOfVerifiers(1)
for (i in 1..10) { for (i in 1..10) {
alice.rpc.startFlow(::CashPaymentFlow, 10.DOLLARS, alice.nodeInfo.legalIdentity).returnValue.get() alice.rpc.startFlow(::CashPaymentFlow, 10.DOLLARS, alice.nodeInfo.legalIdentity).returnValue.get()