diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index 797d857bd6..3d98864cb4 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -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 diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 8ef546ab54..92febedcd0 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -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), diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 601e8c6b11..3133e1c3b0 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -421,6 +421,29 @@ inline fun > CordaRPCOps.startTrac arg3: D ): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3) +@Suppress("unused") +inline fun > CordaRPCOps.startTrackedFlow( + @Suppress("unused_parameter") + flowConstructor: (A, B, C, D, E) -> R, + arg0: A, + arg1: B, + arg2: C, + arg3: D, + arg4: E +): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4) + +@Suppress("unused") +inline fun > 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 = 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. */ diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index d67cd2b010..5925b6c35c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -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(0) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 6eb3c270ef..a5d12f5e78 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -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 ------------ diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index aec0c46e78..6f7f39ceae 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -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. diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt index e5279ef579..4a795d38a3 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt @@ -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 } } diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt index 142c0e2920..63b6e096e7 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt @@ -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 diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 7fbce2ed2e..87a598eb03 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -65,10 +65,11 @@ class CustomVaultQueryTest { private fun issueCashForCurrency(amountToIssue: Amount) { // 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() } diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index b860e43c02..7d0b631e10 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -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 diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index ef6596cf20..dbc884af41 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -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 ------------ diff --git a/finance/src/main/kotlin/net/corda/flows/CashFlowCommand.kt b/finance/src/main/kotlin/net/corda/flows/CashFlowCommand.kt index 446cca5b4d..7dec787ee6 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashFlowCommand.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashFlowCommand.kt @@ -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) } /** diff --git a/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt index 29d51996f5..0fde7d8056 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt @@ -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, - val issueRef: OpaqueBytes, - val recipient: Party, + val issueTo: Party, + val issuerBankParty + : Party, + val issuerBankPartyRef: OpaqueBytes, val notary: Party, val anonymous: Boolean, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { constructor(amount: Amount, - 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, - 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() } - 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) diff --git a/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt b/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt deleted file mode 100644 index 51c885d176..0000000000 --- a/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt +++ /dev/null @@ -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, - 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, - val issueToParty: Party, - val issueToPartyRef: OpaqueBytes, - val issuerBankParty: Party, - val notaryParty: Party, - val anonymous: Boolean) : FlowLogic() { - @Suspendable - @Throws(CashException::class) - override fun call(): AbstractCashFlow.Result { - val issueRequest = IssuanceRequestState(amount, issueToParty, issueToPartyRef, notaryParty, anonymous) - return sendAndReceive(issuerBankParty, issueRequest).unwrap { res -> - val tx = res.stx.tx - val expectedAmount = Amount(amount.quantity, Issued(issuerBankParty.ref(issueToPartyRef), amount.token)) - val cashOutputs = tx.filterOutputs { 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() { - 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(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, - 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 - } - } -} \ No newline at end of file diff --git a/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt index 44688436d6..4a15e5a16d 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt @@ -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() } diff --git a/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt index 9e56aa7105..8bc11139eb 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt @@ -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().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 { future.getOrThrow() diff --git a/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt index a934d92127..673b6940ac 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt @@ -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() } diff --git a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt b/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt deleted file mode 100644 index 5a76d09aa7..0000000000 --- a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt +++ /dev/null @@ -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> { - 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(criteria) - val (_, vaultUpdatesBankClient) = bankClientNode.services.vaultQueryService.trackBy(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 { - 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(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, - issueToParty: Party, - ref: OpaqueBytes, - notaryParty: Party): CordaFuture { - val issueToPartyAndRef = issueToParty.ref(ref) - val issueRequest = IssuanceRequester(amount, issueToParty, issueToPartyAndRef.reference, issuerNode.info.legalIdentity, notaryParty, - anonymous) - return issueToNode.services.startFlow(issueRequest).resultFuture - } -} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 2206df75ba..a5a0b9b7e0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -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() diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 316c661234..431365f7e2 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -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 ) diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 8eed891813..d905a603f7 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -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 diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index 0b1ddf7076..d2a42095f2 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -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 { diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt index bfeeb8691f..e804a51777 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt @@ -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())) + val bocManager = User("bocManager", "password1", permissions = setOf(startFlowPermission())) 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() diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index eda775af3f..4390b85298 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -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(), - startFlowPermission(), + startFlowPermission(), startFlowPermission())) val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf(startFlowPermission())) startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type))) diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt index c5e6e96263..944585d1bf 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt @@ -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 } } diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt index 5524c4ba30..b275d7717d 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt @@ -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) { diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt index 101224dde2..942832f930 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt @@ -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() diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt index d376d21ca8..ebbe86f80d 100644 --- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt @@ -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) { val permissions = setOf( - startFlowPermission(), + startFlowPermission(), startFlowPermission()) val demoUser = listOf(User("demo", "demo", permissions)) driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) { - val user = User("user1", "test", permissions = setOf(startFlowPermission())) + val user = User("user1", "test", permissions = setOf(startFlowPermission(), + startFlowPermission(), + startFlowPermission())) startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type))) startNode(DUMMY_BANK_A.name, rpcUsers = demoUser) startNode(DUMMY_BANK_B.name, rpcUsers = demoUser) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt index 0057bbe945..4d16f3ad80 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/NewTransaction.kt @@ -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