mirror of
https://github.com/corda/corda.git
synced 2025-05-29 21:54:26 +00:00
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:
parent
3888635055
commit
89476904fc
@ -111,8 +111,9 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
|||||||
val anonymous = false
|
val anonymous = false
|
||||||
rpc.startFlow(::CashIssueFlow,
|
rpc.startFlow(::CashIssueFlow,
|
||||||
Amount(100, USD),
|
Amount(100, USD),
|
||||||
OpaqueBytes(ByteArray(1, { 1 })),
|
|
||||||
aliceNode.legalIdentity,
|
aliceNode.legalIdentity,
|
||||||
|
rpc.nodeIdentity().legalIdentity,
|
||||||
|
OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
notaryNode.notaryIdentity,
|
notaryNode.notaryIdentity,
|
||||||
anonymous
|
anonymous
|
||||||
)
|
)
|
||||||
@ -136,7 +137,8 @@ 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, 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()
|
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity, anonymous).returnValue.getOrThrow()
|
||||||
|
|
||||||
var issueSmId: StateMachineRunId? = null
|
var issueSmId: StateMachineRunId? = null
|
||||||
|
@ -77,9 +77,10 @@ class CordaRPCClientTest : NodeBasedTest() {
|
|||||||
login(rpcUser.username, rpcUser.password)
|
login(rpcUser.username, rpcUser.password)
|
||||||
println("Creating proxy")
|
println("Creating proxy")
|
||||||
println("Starting flow")
|
println("Starting flow")
|
||||||
val flowHandle = connection!!.proxy.startTrackedFlow(
|
val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow,
|
||||||
::CashIssueFlow,
|
20.DOLLARS, node.info.legalIdentity,
|
||||||
20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity, 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 {
|
||||||
println("PROGRESS $it")
|
println("PROGRESS $it")
|
||||||
@ -113,8 +114,8 @@ 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, OpaqueBytes.of(0),
|
123.DOLLARS, node.info.legalIdentity,
|
||||||
node.info.legalIdentity, 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()
|
||||||
@ -140,10 +141,11 @@ class CordaRPCClientTest : NodeBasedTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val nodeIdentity = node.info.legalIdentity
|
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,
|
proxy.startFlow(::CashIssueFlow,
|
||||||
123.DOLLARS, OpaqueBytes.of(0),
|
123.DOLLARS, nodeIdentity,
|
||||||
nodeIdentity, nodeIdentity
|
nodeIdentity, OpaqueBytes.of(0), nodeIdentity,
|
||||||
|
true
|
||||||
).returnValue.getOrThrow()
|
).returnValue.getOrThrow()
|
||||||
proxy.startFlowDynamic(CashIssueFlow::class.java,
|
proxy.startFlowDynamic(CashIssueFlow::class.java,
|
||||||
1000.DOLLARS, OpaqueBytes.of(0),
|
1000.DOLLARS, OpaqueBytes.of(0),
|
||||||
|
@ -421,6 +421,29 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrac
|
|||||||
arg3: D
|
arg3: D
|
||||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
): 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.
|
* The Data feed contains a snapshot of the requested data and an [Observable] of future updates.
|
||||||
*/
|
*/
|
||||||
|
@ -176,7 +176,7 @@ class ContractUpgradeFlowTest {
|
|||||||
fun `upgrade Cash to v2`() {
|
fun `upgrade Cash to v2`() {
|
||||||
// Create some cash.
|
// Create some cash.
|
||||||
val anonymous = false
|
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()
|
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)
|
||||||
|
@ -42,6 +42,9 @@ UNRELEASED
|
|||||||
|
|
||||||
* Currency-related API in ``net.corda.core.contracts.ContractsDSL`` has moved to ```net.corda.finance.CurrencyUtils`.
|
* 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
|
Milestone 14
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -67,9 +67,11 @@ class IntegrationTestingTutorial {
|
|||||||
thread {
|
thread {
|
||||||
futures.push(aliceProxy.startFlow(::CashIssueFlow,
|
futures.push(aliceProxy.startFlow(::CashIssueFlow,
|
||||||
i.DOLLARS,
|
i.DOLLARS,
|
||||||
issueRef,
|
|
||||||
bob.nodeInfo.legalIdentity,
|
bob.nodeInfo.legalIdentity,
|
||||||
notary.nodeInfo.notaryIdentity
|
alice.nodeInfo.legalIdentity,
|
||||||
|
issueRef,
|
||||||
|
notary.nodeInfo.notaryIdentity,
|
||||||
|
true
|
||||||
).returnValue)
|
).returnValue)
|
||||||
}
|
}
|
||||||
}.forEach(Thread::join) // Ensure the stack of futures is populated.
|
}.forEach(Thread::join) // Ensure the stack of futures is populated.
|
||||||
|
@ -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), issueRef, me, notary)
|
proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), me, me, issueRef, notary, true)
|
||||||
ownedQuantity += quantity
|
ownedQuantity += quantity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,9 @@ object TopupIssuerFlow {
|
|||||||
val notaryParty = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
|
val notaryParty = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
|
||||||
// invoke Cash subflow to issue Asset
|
// invoke Cash subflow to issue Asset
|
||||||
progressTracker.currentStep = ISSUING
|
progressTracker.currentStep = ISSUING
|
||||||
|
val issuer = serviceHub.myInfo.legalIdentity
|
||||||
val issueRecipient = 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)
|
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
|
||||||
|
@ -65,10 +65,11 @@ 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,
|
||||||
OpaqueBytes.of(0x01),
|
|
||||||
nodeA.info.legalIdentity,
|
nodeA.info.legalIdentity,
|
||||||
|
nodeA.info.legalIdentity,
|
||||||
|
OpaqueBytes.of(0x01),
|
||||||
notaryNode.info.notaryIdentity,
|
notaryNode.info.notaryIdentity,
|
||||||
false))
|
anonymous = false))
|
||||||
// Wait for the flow to stop and print
|
// Wait for the flow to stop and print
|
||||||
flowHandle1.resultFuture.getOrThrow()
|
flowHandle1.resultFuture.getOrThrow()
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,9 @@ 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),
|
||||||
OpaqueBytes.of(0x01),
|
|
||||||
nodeA.info.legalIdentity,
|
nodeA.info.legalIdentity,
|
||||||
|
nodeA.info.legalIdentity,
|
||||||
|
OpaqueBytes.of(0x01),
|
||||||
notaryNode.info.notaryIdentity,
|
notaryNode.info.notaryIdentity,
|
||||||
false))
|
false))
|
||||||
// Wait for the flow to stop and print
|
// Wait for the flow to stop and print
|
||||||
@ -55,8 +56,9 @@ class FxTransactionBuildTutorialTest {
|
|||||||
|
|
||||||
// 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),
|
||||||
OpaqueBytes.of(0x01),
|
|
||||||
nodeB.info.legalIdentity,
|
nodeB.info.legalIdentity,
|
||||||
|
nodeB.info.legalIdentity,
|
||||||
|
OpaqueBytes.of(0x01),
|
||||||
notaryNode.info.notaryIdentity,
|
notaryNode.info.notaryIdentity,
|
||||||
false))
|
false))
|
||||||
// Wait for flow to come to an end and print
|
// Wait for flow to come to an end and print
|
||||||
|
@ -8,6 +8,8 @@ Unreleased
|
|||||||
|
|
||||||
* Merged handling of well known and confidential identities in the identity service.
|
* 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
|
Milestone 14
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ 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, issueRef, recipient, notary, anonymous)
|
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashIssueFlow, amount, recipient, proxy.nodeIdentity().legalIdentity, issueRef, notary, anonymous)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,39 +18,43 @@ import java.util.*
|
|||||||
* Initiates a flow that produces cash issuance transaction.
|
* Initiates a flow that produces cash issuance transaction.
|
||||||
*
|
*
|
||||||
* @param amount the amount of currency to issue.
|
* @param amount the amount of currency to issue.
|
||||||
* @param issueRef a reference to put on the issued currency.
|
* @param issuerBankPartyRef a reference to put on the issued currency.
|
||||||
* @param recipient the party who should own the currency after it is issued.
|
* @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 issueRef: OpaqueBytes,
|
val issueTo: Party,
|
||||||
val recipient: Party,
|
val issuerBankParty
|
||||||
|
: Party,
|
||||||
|
val issuerBankPartyRef: OpaqueBytes,
|
||||||
val notary: Party,
|
val notary: Party,
|
||||||
val anonymous: Boolean,
|
val anonymous: Boolean,
|
||||||
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||||
constructor(amount: Amount<Currency>,
|
constructor(amount: Amount<Currency>,
|
||||||
issueRef: OpaqueBytes,
|
issuerBankPartyRef: OpaqueBytes,
|
||||||
recipient: Party,
|
issuerBankParty: Party,
|
||||||
notary: Party) : this(amount, issueRef, recipient, notary, true, tracker())
|
issueTo: Party,
|
||||||
|
notary: Party) : this(amount, issueTo, issuerBankParty, issuerBankPartyRef, notary, true, tracker())
|
||||||
constructor(amount: Amount<Currency>,
|
constructor(amount: Amount<Currency>,
|
||||||
issueRef: OpaqueBytes,
|
issueTo: Party,
|
||||||
recipient: Party,
|
issuerBankParty: Party,
|
||||||
|
issuerBankPartyRef: OpaqueBytes,
|
||||||
notary: Party,
|
notary: Party,
|
||||||
anonymous: Boolean) : this(amount, issueRef, recipient, notary, anonymous, tracker())
|
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
|
progressTracker.currentStep = GENERATING_ID
|
||||||
val txIdentities = if (anonymous) {
|
val txIdentities = if (anonymous) {
|
||||||
subFlow(TransactionKeyFlow(recipient))
|
subFlow(TransactionKeyFlow(issueTo))
|
||||||
} else {
|
} else {
|
||||||
emptyMap<Party, AnonymousParty>()
|
emptyMap<Party, AnonymousParty>()
|
||||||
}
|
}
|
||||||
val anonymousRecipient = txIdentities[recipient] ?: recipient
|
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 = serviceHub.myInfo.legalIdentity.ref(issueRef)
|
val issuer = issuerBankParty.ref(issuerBankPartyRef)
|
||||||
val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), anonymousRecipient, notary)
|
val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), anonymousRecipient, notary)
|
||||||
progressTracker.currentStep = SIGNING_TX
|
progressTracker.currentStep = SIGNING_TX
|
||||||
val tx = serviceHub.signInitialTransaction(builder, signers)
|
val tx = serviceHub.signInitialTransaction(builder, signers)
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,9 +33,11 @@ class CashExitFlowTests {
|
|||||||
bankOfCorda = bankOfCordaNode.info.legalIdentity
|
bankOfCorda = bankOfCordaNode.info.legalIdentity
|
||||||
|
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
|
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance,
|
||||||
bankOfCorda,
|
bankOfCorda,
|
||||||
notary)).resultFuture
|
bankOfCorda, ref,
|
||||||
|
notary,
|
||||||
|
anonymous = true)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
future.getOrThrow()
|
future.getOrThrow()
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,12 @@ 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, ref,
|
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected,
|
||||||
bankOfCorda,
|
bankOfCorda,
|
||||||
notary)).resultFuture
|
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()
|
||||||
@ -54,9 +57,13 @@ class CashIssueFlowTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `issue zero cash`() {
|
fun `issue zero cash`() {
|
||||||
val expected = 0.DOLLARS
|
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,
|
bankOfCorda,
|
||||||
notary)).resultFuture
|
bankOfCorda,
|
||||||
|
ref,
|
||||||
|
notary,
|
||||||
|
anonymous = true)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertFailsWith<IllegalArgumentException> {
|
assertFailsWith<IllegalArgumentException> {
|
||||||
future.getOrThrow()
|
future.getOrThrow()
|
||||||
|
@ -37,9 +37,12 @@ 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, ref,
|
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance,
|
||||||
bankOfCorda,
|
bankOfCorda,
|
||||||
notary)).resultFuture
|
bankOfCorda,
|
||||||
|
ref,
|
||||||
|
notary,
|
||||||
|
true)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
future.getOrThrow()
|
future.getOrThrow()
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,7 +29,6 @@ import net.corda.core.utilities.toNonEmptySet
|
|||||||
import net.corda.flows.CashExitFlow
|
import net.corda.flows.CashExitFlow
|
||||||
import net.corda.flows.CashIssueFlow
|
import net.corda.flows.CashIssueFlow
|
||||||
import net.corda.flows.CashPaymentFlow
|
import net.corda.flows.CashPaymentFlow
|
||||||
import net.corda.flows.IssuerFlow
|
|
||||||
import net.corda.node.services.ContractUpgradeHandler
|
import net.corda.node.services.ContractUpgradeHandler
|
||||||
import net.corda.node.services.NotaryChangeHandler
|
import net.corda.node.services.NotaryChangeHandler
|
||||||
import net.corda.node.services.NotifyTransactionHandler
|
import net.corda.node.services.NotifyTransactionHandler
|
||||||
@ -210,9 +209,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
findRPCFlows(scanResult)
|
findRPCFlows(scanResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove this once the cash stuff is in its own CorDapp
|
|
||||||
registerInitiatedFlow(IssuerFlow.Issuer::class.java)
|
|
||||||
|
|
||||||
runOnStop += network::stop
|
runOnStop += network::stop
|
||||||
_networkMapRegistrationFuture.captureLater(registerWithNetworkMapIfConfigured())
|
_networkMapRegistrationFuture.captureLater(registerWithNetworkMapIfConfigured())
|
||||||
smm.start()
|
smm.start()
|
||||||
|
@ -95,7 +95,7 @@ class CordaRPCOpsImplTest {
|
|||||||
// Tell the monitoring service node to issue some cash
|
// Tell the monitoring service node to issue some cash
|
||||||
val anonymous = false
|
val anonymous = false
|
||||||
val recipient = aliceNode.info.legalIdentity
|
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()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
var issueSmId: StateMachineRunId? = null
|
var issueSmId: StateMachineRunId? = null
|
||||||
@ -133,8 +133,9 @@ class CordaRPCOpsImplTest {
|
|||||||
val anonymous = false
|
val anonymous = false
|
||||||
val result = rpc.startFlow(::CashIssueFlow,
|
val result = rpc.startFlow(::CashIssueFlow,
|
||||||
100.DOLLARS,
|
100.DOLLARS,
|
||||||
OpaqueBytes(ByteArray(1, { 1 })),
|
|
||||||
aliceNode.info.legalIdentity,
|
aliceNode.info.legalIdentity,
|
||||||
|
rpc.nodeIdentity().legalIdentity,
|
||||||
|
OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
notaryNode.info.notaryIdentity,
|
notaryNode.info.notaryIdentity,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
@ -213,8 +214,9 @@ class CordaRPCOpsImplTest {
|
|||||||
assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
|
assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
|
||||||
rpc.startFlow(::CashIssueFlow,
|
rpc.startFlow(::CashIssueFlow,
|
||||||
Amount(100, USD),
|
Amount(100, USD),
|
||||||
OpaqueBytes(ByteArray(1, { 1 })),
|
|
||||||
aliceNode.info.legalIdentity,
|
aliceNode.info.legalIdentity,
|
||||||
|
rpc.nodeIdentity().legalIdentity,
|
||||||
|
OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
notaryNode.info.notaryIdentity,
|
notaryNode.info.notaryIdentity,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
@ -330,8 +330,9 @@ 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,
|
||||||
OpaqueBytes.of(0x01),
|
|
||||||
node1.info.legalIdentity,
|
node1.info.legalIdentity,
|
||||||
|
node1.info.legalIdentity,
|
||||||
|
OpaqueBytes.of(0x01),
|
||||||
notary1.info.notaryIdentity,
|
notary1.info.notaryIdentity,
|
||||||
anonymous = false))
|
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
|
||||||
|
@ -68,7 +68,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
'password' : "test",
|
'password' : "test",
|
||||||
'permissions': ["StartFlow.net.corda.flows.CashPaymentFlow",
|
'permissions': ["StartFlow.net.corda.flows.CashPaymentFlow",
|
||||||
"StartFlow.net.corda.flows.CashExitFlow",
|
"StartFlow.net.corda.flows.CashExitFlow",
|
||||||
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]]
|
"StartFlow.net.corda.flows.CashIssueFlow"]]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package net.corda.bank
|
package net.corda.bank
|
||||||
|
|
||||||
import net.corda.contracts.asset.Cash
|
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.messaging.startFlow
|
||||||
import net.corda.core.node.services.ServiceInfo
|
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.node.services.vault.QueryCriteria
|
import net.corda.core.node.services.vault.QueryCriteria
|
||||||
import net.corda.core.internal.concurrent.transpose
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.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 +20,7 @@ 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<IssuanceRequester>()))
|
val bocManager = User("bocManager", "password1", permissions = setOf(startFlowPermission<CashIssueFlow>()))
|
||||||
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)),
|
||||||
@ -45,11 +45,11 @@ class BankOfCordaRPCClientTest {
|
|||||||
// Kick-off actual Issuer Flow
|
// Kick-off actual Issuer Flow
|
||||||
val anonymous = true
|
val anonymous = true
|
||||||
bocProxy.startFlow(
|
bocProxy.startFlow(
|
||||||
::IssuanceRequester,
|
::CashIssueFlow,
|
||||||
1000.DOLLARS,
|
1000.DOLLARS,
|
||||||
nodeBigCorporation.nodeInfo.legalIdentity,
|
nodeBigCorporation.nodeInfo.legalIdentity,
|
||||||
BIG_CORP_PARTY_REF,
|
|
||||||
nodeBankOfCorda.nodeInfo.legalIdentity,
|
nodeBankOfCorda.nodeInfo.legalIdentity,
|
||||||
|
BIG_CORP_PARTY_REF,
|
||||||
nodeBankOfCorda.nodeInfo.notaryIdentity,
|
nodeBankOfCorda.nodeInfo.notaryIdentity,
|
||||||
anonymous).returnValue.getOrThrow()
|
anonymous).returnValue.getOrThrow()
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ import net.corda.core.node.services.ServiceType
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.flows.CashExitFlow
|
import net.corda.flows.CashExitFlow
|
||||||
|
import net.corda.flows.CashIssueFlow
|
||||||
import net.corda.flows.CashPaymentFlow
|
import net.corda.flows.CashPaymentFlow
|
||||||
import net.corda.flows.IssuerFlow
|
|
||||||
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
|
||||||
@ -68,7 +68,7 @@ private class BankOfCordaDriver {
|
|||||||
"test",
|
"test",
|
||||||
permissions = setOf(
|
permissions = setOf(
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlowPermission<CashPaymentFlow>(),
|
||||||
startFlowPermission<IssuerFlow.IssuanceRequester>(),
|
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)))
|
||||||
|
@ -8,7 +8,7 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
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.IssuerFlow.IssuanceRequester
|
import net.corda.flows.CashIssueFlow
|
||||||
import net.corda.testing.http.HttpApi
|
import net.corda.testing.http.HttpApi
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -46,9 +46,9 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
|
|||||||
?: throw IllegalStateException("Unable to locate notary node in network map cache")
|
?: throw IllegalStateException("Unable to locate notary node in network map cache")
|
||||||
|
|
||||||
val amount = Amount(params.amount, Currency.getInstance(params.currency))
|
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
|
.returnValue.getOrThrow().stx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ 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 net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.flows.IssuerFlow.IssuanceRequester
|
import net.corda.flows.CashIssueFlow
|
||||||
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.*
|
||||||
@ -18,7 +18,7 @@ import javax.ws.rs.core.Response
|
|||||||
@Path("bank")
|
@Path("bank")
|
||||||
class BankOfCordaWebApi(val rpc: CordaRPCOps) {
|
class BankOfCordaWebApi(val rpc: CordaRPCOps) {
|
||||||
data class IssueRequestParams(val amount: Long, val currency: String,
|
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 issuerBankName: X500Name,
|
||||||
val notaryName: X500Name,
|
val notaryName: X500Name,
|
||||||
val anonymous: Boolean)
|
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()
|
?: 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 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
|
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(::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")
|
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) {
|
||||||
|
@ -54,7 +54,8 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
|
|||||||
val anonymous = false
|
val anonymous = false
|
||||||
// issue random amounts of currency up to the requested amount, in parallel
|
// issue 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), 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()
|
resultFutures.transpose().getOrThrow()
|
||||||
|
@ -2,15 +2,16 @@ package net.corda.traderdemo
|
|||||||
|
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.testing.DUMMY_BANK_A
|
import net.corda.flows.CashIssueFlow
|
||||||
import net.corda.testing.DUMMY_BANK_B
|
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
|
||||||
import net.corda.flows.IssuerFlow
|
|
||||||
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
|
||||||
import net.corda.testing.BOC
|
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.testing.driver.driver
|
||||||
|
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
|
||||||
import net.corda.traderdemo.flow.SellerFlow
|
import net.corda.traderdemo.flow.SellerFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,11 +20,13 @@ import net.corda.traderdemo.flow.SellerFlow
|
|||||||
*/
|
*/
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val permissions = setOf(
|
val permissions = setOf(
|
||||||
startFlowPermission<IssuerFlow.IssuanceRequester>(),
|
startFlowPermission<CashIssueFlow>(),
|
||||||
startFlowPermission<SellerFlow>())
|
startFlowPermission<SellerFlow>())
|
||||||
val demoUser = listOf(User("demo", "demo", permissions))
|
val demoUser = listOf(User("demo", "demo", permissions))
|
||||||
driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) {
|
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_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser)
|
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser)
|
||||||
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser)
|
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser)
|
||||||
|
@ -26,8 +26,8 @@ import net.corda.core.messaging.FlowHandle
|
|||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.explorer.formatters.PartyNameFormatter
|
import net.corda.explorer.formatters.PartyNameFormatter
|
||||||
import net.corda.explorer.model.CashTransaction
|
import net.corda.explorer.model.CashTransaction
|
||||||
import net.corda.explorer.model.IssuerModel
|
import net.corda.explorer.model.IssuerModel
|
||||||
|
Loading…
x
Reference in New Issue
Block a user