Remove IssuerFlow

* Remove IssuerFlow as it is dangerous and its presence in the finance module risks accidental use in non-test code. As written it will issue arbitary amounts of currency on request from any node on the network, with no validation barring that the currency type is valid.
* Unify interface to CashIssueFlow to match the previous IssuerFlow
This commit is contained in:
Ross Nicoll 2017-08-11 14:03:23 +01:00
parent 3888635055
commit 89476904fc
29 changed files with 130 additions and 363 deletions

View File

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

View File

@ -77,9 +77,10 @@ class CordaRPCClientTest : NodeBasedTest() {
login(rpcUser.username, rpcUser.password)
println("Creating proxy")
println("Starting flow")
val flowHandle = connection!!.proxy.startTrackedFlow(
::CashIssueFlow,
20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity, node.info.legalIdentity)
val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow,
20.DOLLARS, node.info.legalIdentity,
node.info.legalIdentity, OpaqueBytes.of(0), node.info.legalIdentity, true
)
println("Started flow, waiting on result")
flowHandle.progress.subscribe {
println("PROGRESS $it")
@ -113,8 +114,8 @@ class CordaRPCClientTest : NodeBasedTest() {
assertTrue(startCash.isEmpty(), "Should not start with any cash")
val flowHandle = proxy.startFlow(::CashIssueFlow,
123.DOLLARS, OpaqueBytes.of(0),
node.info.legalIdentity, node.info.legalIdentity
123.DOLLARS, node.info.legalIdentity,
node.info.legalIdentity, OpaqueBytes.of(0), node.info.legalIdentity, true
)
println("Started issuing cash, waiting on result")
flowHandle.returnValue.get()
@ -140,10 +141,11 @@ class CordaRPCClientTest : NodeBasedTest() {
}
}
val nodeIdentity = node.info.legalIdentity
node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity, nodeIdentity), FlowInitiator.Shell).resultFuture.getOrThrow()
node.services.startFlow(CashIssueFlow(2000.DOLLARS, nodeIdentity, nodeIdentity, OpaqueBytes.of(0), nodeIdentity, true), FlowInitiator.Shell).resultFuture.getOrThrow()
proxy.startFlow(::CashIssueFlow,
123.DOLLARS, OpaqueBytes.of(0),
nodeIdentity, nodeIdentity
123.DOLLARS, nodeIdentity,
nodeIdentity, OpaqueBytes.of(0), nodeIdentity,
true
).returnValue.getOrThrow()
proxy.startFlowDynamic(CashIssueFlow::class.java,
1000.DOLLARS, OpaqueBytes.of(0),

View File

@ -421,6 +421,29 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrac
arg3: D
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
@Suppress("unused")
inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C, D, E) -> R,
arg0: A,
arg1: B,
arg2: C,
arg3: D,
arg4: E
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
@Suppress("unused")
inline fun <T : Any, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C, D, E, F) -> R,
arg0: A,
arg1: B,
arg2: C,
arg3: D,
arg4: E,
arg5: F
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4, arg5)
/**
* The Data feed contains a snapshot of the requested data and an [Observable] of future updates.
*/

View File

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

View File

@ -42,6 +42,9 @@ UNRELEASED
* Currency-related API in ``net.corda.core.contracts.ContractsDSL`` has moved to ```net.corda.finance.CurrencyUtils`.
* Remove `IssuerFlow` as it allowed nodes to request arbitrary amounts of cash to be issued from any remote node. Use
`CashIssueFlow` instead.
Milestone 14
------------

View File

@ -67,9 +67,11 @@ class IntegrationTestingTutorial {
thread {
futures.push(aliceProxy.startFlow(::CashIssueFlow,
i.DOLLARS,
issueRef,
bob.nodeInfo.legalIdentity,
notary.nodeInfo.notaryIdentity
alice.nodeInfo.legalIdentity,
issueRef,
notary.nodeInfo.notaryIdentity,
true
).returnValue)
}
}.forEach(Thread::join) // Ensure the stack of futures is populated.

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,8 @@ Unreleased
* Merged handling of well known and confidential identities in the identity service.
* Remove `IssuerFlow` as it allowed nodes to request arbitrary amounts of cash to be issued from any remote node.
Milestone 14
------------

View File

@ -22,7 +22,7 @@ sealed class CashFlowCommand {
val recipient: Party,
val notary: Party,
val anonymous: Boolean) : CashFlowCommand() {
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashIssueFlow, amount, issueRef, recipient, notary, anonymous)
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashIssueFlow, amount, recipient, proxy.nodeIdentity().legalIdentity, issueRef, notary, anonymous)
}
/**

View File

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

View File

@ -1,123 +0,0 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.core.contracts.FungibleAsset
import net.corda.core.contracts.Issued
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import net.corda.finance.CHF
import net.corda.finance.EUR
import net.corda.finance.GBP
import net.corda.finance.USD
import java.util.*
/**
* This flow enables a client to request issuance of some [FungibleAsset] from a
* server acting as an issuer (see [Issued]) of FungibleAssets.
*
* It is not intended for production usage, but rather for experimentation and testing purposes where it may be
* useful for creation of fake assets.
*/
object IssuerFlow {
@CordaSerializable
data class IssuanceRequestState(val amount: Amount<Currency>,
val issueToParty: Party,
val issuerPartyRef: OpaqueBytes,
val notaryParty: Party,
val anonymous: Boolean)
/**
* IssuanceRequester should be used by a client to ask a remote node to issue some [FungibleAsset] with the given details.
* Returns the transaction created by the Issuer to move the cash to the Requester.
*
* @param anonymous true if the issued asset should be sent to a new confidential identity, false to send it to the
* well known identity (generally this is only used in testing).
*/
@InitiatingFlow
@StartableByRPC
class IssuanceRequester(val amount: Amount<Currency>,
val issueToParty: Party,
val issueToPartyRef: OpaqueBytes,
val issuerBankParty: Party,
val notaryParty: Party,
val anonymous: Boolean) : FlowLogic<AbstractCashFlow.Result>() {
@Suspendable
@Throws(CashException::class)
override fun call(): AbstractCashFlow.Result {
val issueRequest = IssuanceRequestState(amount, issueToParty, issueToPartyRef, notaryParty, anonymous)
return sendAndReceive<AbstractCashFlow.Result>(issuerBankParty, issueRequest).unwrap { res ->
val tx = res.stx.tx
val expectedAmount = Amount(amount.quantity, Issued(issuerBankParty.ref(issueToPartyRef), amount.token))
val cashOutputs = tx.filterOutputs<Cash.State> { state -> state.owner == res.recipient }
require(cashOutputs.size == 1) { "Require a single cash output paying ${res.recipient}, found ${tx.outputs}" }
require(cashOutputs.single().amount == expectedAmount) { "Require payment of $expectedAmount"}
res
}
}
}
/**
* Issuer refers to a Node acting as a Bank Issuer of [FungibleAsset], and processes requests from a [IssuanceRequester] client.
* Returns the generated transaction representing the transfer of the [Issued] [FungibleAsset] to the issue requester.
*/
@InitiatedBy(IssuanceRequester::class)
class Issuer(val otherParty: Party) : FlowLogic<SignedTransaction>() {
companion object {
object AWAITING_REQUEST : ProgressTracker.Step("Awaiting issuance request")
object ISSUING : ProgressTracker.Step("Self issuing asset")
object TRANSFERRING : ProgressTracker.Step("Transferring asset to issuance requester")
object SENDING_CONFIRM : ProgressTracker.Step("Confirming asset issuance to requester")
fun tracker() = ProgressTracker(AWAITING_REQUEST, ISSUING, TRANSFERRING, SENDING_CONFIRM)
private val VALID_CURRENCIES = listOf(USD, GBP, EUR, CHF)
}
override val progressTracker: ProgressTracker = tracker()
@Suspendable
@Throws(CashException::class)
override fun call(): SignedTransaction {
progressTracker.currentStep = AWAITING_REQUEST
val issueRequest = receive<IssuanceRequestState>(otherParty).unwrap {
// validate request inputs (for example, lets restrict the types of currency that can be issued)
if (it.amount.token !in VALID_CURRENCIES) throw FlowException("Currency must be one of $VALID_CURRENCIES")
it
}
// TODO: parse request to determine Asset to issue
val txn = issueCashTo(issueRequest.amount, issueRequest.issueToParty, issueRequest.issuerPartyRef, issueRequest.notaryParty, issueRequest.anonymous)
progressTracker.currentStep = SENDING_CONFIRM
send(otherParty, txn)
return txn.stx
}
@Suspendable
private fun issueCashTo(amount: Amount<Currency>,
issueTo: Party,
issuerPartyRef: OpaqueBytes,
notaryParty: Party,
anonymous: Boolean): AbstractCashFlow.Result {
// invoke Cash subflow to issue Asset
progressTracker.currentStep = ISSUING
val issueRecipient = serviceHub.myInfo.legalIdentity
val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, issueRecipient, notaryParty, anonymous)
val issueTx = subFlow(issueCashFlow)
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
// short-circuit when issuing to self
if (issueTo == serviceHub.myInfo.legalIdentity)
return issueTx
// now invoke Cash subflow to Move issued assetType to issue requester
progressTracker.currentStep = TRANSFERRING
val moveCashFlow = CashPaymentFlow(amount, issueTo, anonymous)
val moveTx = subFlow(moveCashFlow)
// NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger)
return moveTx
}
}
}

View File

@ -33,9 +33,11 @@ class CashExitFlowTests {
bankOfCorda = bankOfCordaNode.info.legalIdentity
mockNet.runNetwork()
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance,
bankOfCorda,
notary)).resultFuture
bankOfCorda, ref,
notary,
anonymous = true)).resultFuture
mockNet.runNetwork()
future.getOrThrow()
}

View File

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

View File

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

View File

@ -1,167 +0,0 @@
package net.corda.flows
import net.corda.contracts.asset.Cash
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Amount
import net.corda.core.flows.FlowException
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.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.flows.IssuerFlow.IssuanceRequester
import net.corda.testing.contracts.calculateRandomlySizedAmounts
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import net.corda.testing.sequence
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.*
import kotlin.test.assertFailsWith
@RunWith(Parameterized::class)
class IssuerFlowTest(val anonymous: Boolean) {
companion object {
@Parameterized.Parameters
@JvmStatic
fun data(): Collection<Array<Boolean>> {
return listOf(arrayOf(false), arrayOf(true))
}
}
lateinit var mockNet: MockNetwork
lateinit var notaryNode: MockNode
lateinit var bankOfCordaNode: MockNode
lateinit var bankClientNode: MockNode
@Before
fun start() {
mockNet = MockNetwork(threadPerNode = true)
val basketOfNodes = mockNet.createSomeNodes(2)
bankOfCordaNode = basketOfNodes.partyNodes[0]
bankClientNode = basketOfNodes.partyNodes[1]
notaryNode = basketOfNodes.notaryNode
}
@After
fun cleanUp() {
mockNet.stopNodes()
}
@Test
fun `test issuer flow`() {
val notary = notaryNode.services.myInfo.notaryIdentity
val (vaultUpdatesBoc, vaultUpdatesBankClient) = bankOfCordaNode.database.transaction {
// Register for vault updates
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultQueryService.trackBy<Cash.State>(criteria)
val (_, vaultUpdatesBankClient) = bankClientNode.services.vaultQueryService.trackBy<Cash.State>(criteria)
// using default IssueTo Party Reference
val issuerResult = runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, 1000000.DOLLARS,
bankClientNode.info.legalIdentity, OpaqueBytes.of(123), notary)
issuerResult.get()
Pair(vaultUpdatesBoc, vaultUpdatesBankClient)
}
// Check Bank of Corda Vault Updates
vaultUpdatesBoc.expectEvents {
sequence(
// ISSUE
expect { update ->
require(update.consumed.isEmpty()) { "Expected 0 consumed states, actual: $update" }
require(update.produced.size == 1) { "Expected 1 produced states, actual: $update" }
val issued = update.produced.single().state.data
require(issued.owner.owningKey in bankOfCordaNode.services.keyManagementService.keys)
},
// MOVE
expect { update ->
require(update.consumed.size == 1) { "Expected 1 consumed states, actual: $update" }
require(update.produced.isEmpty()) { "Expected 0 produced states, actual: $update" }
}
)
}
// Check Bank Client Vault Updates
vaultUpdatesBankClient.expectEvents {
// MOVE
expect { (consumed, produced) ->
require(consumed.isEmpty()) { consumed.size }
require(produced.size == 1) { produced.size }
val paidState = produced.single().state.data
require(paidState.owner.owningKey in bankClientNode.services.keyManagementService.keys)
}
}
}
@Test
fun `test issuer flow rejects restricted`() {
val notary = notaryNode.services.myInfo.notaryIdentity
// try to issue an amount of a restricted currency
assertFailsWith<FlowException> {
runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, Amount(100000L, Currency.getInstance("BRL")),
bankClientNode.info.legalIdentity, OpaqueBytes.of(123), notary).getOrThrow()
}
}
@Test
fun `test issue flow to self`() {
val notary = notaryNode.services.myInfo.notaryIdentity
val vaultUpdatesBoc = bankOfCordaNode.database.transaction {
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultQueryService.trackBy<Cash.State>(criteria)
// using default IssueTo Party Reference
runIssuerAndIssueRequester(bankOfCordaNode, bankOfCordaNode, 1000000.DOLLARS,
bankOfCordaNode.info.legalIdentity, OpaqueBytes.of(123), notary).getOrThrow()
vaultUpdatesBoc
}
// Check Bank of Corda Vault Updates
vaultUpdatesBoc.expectEvents {
sequence(
// ISSUE
expect { update ->
require(update.consumed.isEmpty()) { "Expected 0 consumed states, actual: $update" }
require(update.produced.size == 1) { "Expected 1 produced states, actual: $update" }
}
)
}
}
@Test
fun `test concurrent issuer flow`() {
val notary = notaryNode.services.myInfo.notaryIdentity
// this test exercises the Cashflow issue and move subflows to ensure consistent spending of issued states
val amount = 10000.DOLLARS
val amounts = calculateRandomlySizedAmounts(10000.DOLLARS, 10, 10, Random())
val handles = amounts.map { pennies ->
runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, Amount(pennies, amount.token),
bankClientNode.info.legalIdentity, OpaqueBytes.of(123), notary)
}
handles.forEach {
require(it.get().stx is SignedTransaction)
}
}
private fun runIssuerAndIssueRequester(issuerNode: MockNode,
issueToNode: MockNode,
amount: Amount<Currency>,
issueToParty: Party,
ref: OpaqueBytes,
notaryParty: Party): CordaFuture<AbstractCashFlow.Result> {
val issueToPartyAndRef = issueToParty.ref(ref)
val issueRequest = IssuanceRequester(amount, issueToParty, issueToPartyAndRef.reference, issuerNode.info.legalIdentity, notaryParty,
anonymous)
return issueToNode.services.startFlow(issueRequest).resultFuture
}
}

View File

@ -29,7 +29,6 @@ import net.corda.core.utilities.toNonEmptySet
import net.corda.flows.CashExitFlow
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.flows.IssuerFlow
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.NotifyTransactionHandler
@ -210,9 +209,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
findRPCFlows(scanResult)
}
// TODO Remove this once the cash stuff is in its own CorDapp
registerInitiatedFlow(IssuerFlow.Issuer::class.java)
runOnStop += network::stop
_networkMapRegistrationFuture.captureLater(registerWithNetworkMapIfConfigured())
smm.start()

View File

@ -95,7 +95,7 @@ class CordaRPCOpsImplTest {
// Tell the monitoring service node to issue some cash
val anonymous = false
val recipient = aliceNode.info.legalIdentity
val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity, anonymous)
val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), recipient, rpc.nodeIdentity().legalIdentity, ref, notaryNode.info.notaryIdentity, anonymous)
mockNet.runNetwork()
var issueSmId: StateMachineRunId? = null
@ -133,8 +133,9 @@ class CordaRPCOpsImplTest {
val anonymous = false
val result = rpc.startFlow(::CashIssueFlow,
100.DOLLARS,
OpaqueBytes(ByteArray(1, { 1 })),
aliceNode.info.legalIdentity,
rpc.nodeIdentity().legalIdentity,
OpaqueBytes(ByteArray(1, { 1 })),
notaryNode.info.notaryIdentity,
false
)
@ -213,8 +214,9 @@ class CordaRPCOpsImplTest {
assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
rpc.startFlow(::CashIssueFlow,
Amount(100, USD),
OpaqueBytes(ByteArray(1, { 1 })),
aliceNode.info.legalIdentity,
rpc.nodeIdentity().legalIdentity,
OpaqueBytes(ByteArray(1, { 1 })),
notaryNode.info.notaryIdentity,
false
)

View File

@ -330,8 +330,9 @@ class FlowFrameworkTests {
assertEquals(notary1.info.notaryIdentity, notary2.info.notaryIdentity)
node1.services.startFlow(CashIssueFlow(
2000.DOLLARS,
OpaqueBytes.of(0x01),
node1.info.legalIdentity,
node1.info.legalIdentity,
OpaqueBytes.of(0x01),
notary1.info.notaryIdentity,
anonymous = false))
// We pay a couple of times, the notary picking should go round robin

View File

@ -68,7 +68,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
'password' : "test",
'permissions': ["StartFlow.net.corda.flows.CashPaymentFlow",
"StartFlow.net.corda.flows.CashExitFlow",
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]]
"StartFlow.net.corda.flows.CashIssueFlow"]]
]
}
node {

View File

@ -1,14 +1,14 @@
package net.corda.bank
import net.corda.contracts.asset.Cash
import net.corda.finance.DOLLARS
import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.getOrThrow
import net.corda.flows.IssuerFlow.IssuanceRequester
import net.corda.finance.DOLLARS
import net.corda.flows.CashIssueFlow
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
@ -20,7 +20,7 @@ class BankOfCordaRPCClientTest {
@Test
fun `issuer flow via RPC`() {
driver(dsl = {
val bocManager = User("bocManager", "password1", permissions = setOf(startFlowPermission<IssuanceRequester>()))
val bocManager = User("bocManager", "password1", permissions = setOf(startFlowPermission<CashIssueFlow>()))
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet())
val (nodeBankOfCorda, nodeBigCorporation) = listOf(
startNode(BOC.name, setOf(ServiceInfo(SimpleNotaryService.type)), listOf(bocManager)),
@ -45,11 +45,11 @@ class BankOfCordaRPCClientTest {
// Kick-off actual Issuer Flow
val anonymous = true
bocProxy.startFlow(
::IssuanceRequester,
::CashIssueFlow,
1000.DOLLARS,
nodeBigCorporation.nodeInfo.legalIdentity,
BIG_CORP_PARTY_REF,
nodeBankOfCorda.nodeInfo.legalIdentity,
BIG_CORP_PARTY_REF,
nodeBankOfCorda.nodeInfo.notaryIdentity,
anonymous).returnValue.getOrThrow()

View File

@ -8,8 +8,8 @@ import net.corda.core.node.services.ServiceType
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.flows.CashExitFlow
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.flows.IssuerFlow
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
@ -68,7 +68,7 @@ private class BankOfCordaDriver {
"test",
permissions = setOf(
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<IssuerFlow.IssuanceRequester>(),
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashExitFlow>()))
val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf(startFlowPermission<CashPaymentFlow>()))
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))

View File

@ -8,7 +8,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.flows.IssuerFlow.IssuanceRequester
import net.corda.flows.CashIssueFlow
import net.corda.testing.http.HttpApi
import java.util.*
@ -46,9 +46,9 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
?: throw IllegalStateException("Unable to locate notary node in network map cache")
val amount = Amount(params.amount, Currency.getInstance(params.currency))
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
return rpc.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty, notaryNode.notaryIdentity, params.anonymous)
return rpc.startFlow(::CashIssueFlow, amount, issueToParty, issuerBankParty, issuerBankPartyRef, notaryNode.notaryIdentity, params.anonymous)
.returnValue.getOrThrow().stx
}
}

View File

@ -6,7 +6,7 @@ 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.IssuerFlow.IssuanceRequester
import net.corda.flows.CashIssueFlow
import org.bouncycastle.asn1.x500.X500Name
import java.time.LocalDateTime
import java.util.*
@ -18,7 +18,7 @@ import javax.ws.rs.core.Response
@Path("bank")
class BankOfCordaWebApi(val rpc: CordaRPCOps) {
data class IssueRequestParams(val amount: Long, val currency: String,
val issueToPartyName: X500Name, val issueToPartyRefAsString: String,
val issueToPartyName: X500Name, val issuerBankPartyRef: String,
val issuerBankName: X500Name,
val notaryName: X500Name,
val anonymous: Boolean)
@ -52,13 +52,13 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate $notaryParty in network map service").build()
val amount = Amount(params.amount, Currency.getInstance(params.currency))
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
val anonymous = params.anonymous
// invoke client side of Issuer Flow: IssuanceRequester
// The line below blocks and waits for the future to resolve.
return try {
rpc.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty, notaryNode.notaryIdentity, anonymous).returnValue.getOrThrow()
rpc.startFlow(::CashIssueFlow, amount, issueToParty, issuerBankParty, issuerBankPartyRef, notaryNode.notaryIdentity, anonymous).returnValue.getOrThrow()
logger.info("Issue request completed successfully: $params")
Response.status(Response.Status.CREATED).build()
} catch (e: Exception) {

View File

@ -54,7 +54,8 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
val anonymous = false
// issue random amounts of currency up to the requested amount, in parallel
val resultFutures = amounts.map { pennies ->
rpc.startFlow(::CashIssueFlow, amount.copy(quantity = pennies), OpaqueBytes.of(1), buyer, notaryNode.notaryIdentity, anonymous).returnValue
rpc.startFlow(::CashIssueFlow, amount.copy(quantity = pennies), buyer, rpc.nodeIdentity().legalIdentity,
OpaqueBytes.of(1), notaryNode.notaryIdentity, anonymous).returnValue
}
resultFutures.transpose().getOrThrow()

View File

@ -2,15 +2,16 @@ package net.corda.traderdemo
import net.corda.core.internal.div
import net.corda.core.node.services.ServiceInfo
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.DUMMY_NOTARY
import net.corda.flows.IssuerFlow
import net.corda.flows.CashIssueFlow
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import net.corda.testing.BOC
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.driver.driver
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
import net.corda.traderdemo.flow.SellerFlow
/**
@ -19,11 +20,13 @@ import net.corda.traderdemo.flow.SellerFlow
*/
fun main(args: Array<String>) {
val permissions = setOf(
startFlowPermission<IssuerFlow.IssuanceRequester>(),
startFlowPermission<CashIssueFlow>(),
startFlowPermission<SellerFlow>())
val demoUser = listOf(User("demo", "demo", permissions))
driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) {
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
val user = User("user1", "test", permissions = setOf(startFlowPermission<CashIssueFlow>(),
startFlowPermission<CommercialPaperIssueFlow>(),
startFlowPermission<SellerFlow>()))
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser)
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser)

View File

@ -26,8 +26,8 @@ import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.startFlow
import net.corda.core.node.NodeInfo
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.explorer.formatters.PartyNameFormatter
import net.corda.explorer.model.CashTransaction
import net.corda.explorer.model.IssuerModel