From 7dc6f47b3d39d56a81f6840e77e87b829ba312c6 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Mon, 30 Jan 2017 18:34:48 +0000 Subject: [PATCH] Split CashFlow into three flows Split CashFlow into independent CashIssueFlow, CashExitFlow and CashPaymentFlow, so that users can be given access to one but not the other(s). Signed-off-by: Ross Nicoll --- .../net/corda/client/CordaRPCClientTest.kt | 21 ++- .../net/corda/client/NodeMonitorModelTest.kt | 7 +- .../net/corda/client/mock/EventGenerator.kt | 8 +- .../corda/docs/IntegrationTestingTutorial.kt | 8 +- .../net/corda/docs/ClientRpcTutorial.kt | 15 +- .../docs/FxTransactionBuildTutorialTest.kt | 16 +- docs/source/running-the-demos.rst | 4 +- .../net/corda/flows/AbstractCashFlow.kt | 32 ++++ .../kotlin/net/corda/flows/CashExitFlow.kt | 67 +++++++ .../main/kotlin/net/corda/flows/CashFlow.kt | 165 ++++-------------- .../kotlin/net/corda/flows/CashIssueFlow.kt | 46 +++++ .../kotlin/net/corda/flows/CashPaymentFlow.kt | 49 ++++++ .../main/kotlin/net/corda/flows/IssuerFlow.kt | 4 +- .../node/services/DistributedServiceTests.kt | 20 ++- .../net/corda/node/internal/AbstractNode.kt | 20 +-- .../net/corda/node/CordaRPCOpsImplTest.kt | 44 ++--- .../statemachine/StateMachineManagerTests.kt | 12 +- samples/bank-of-corda-demo/README.md | 4 +- samples/bank-of-corda-demo/build.gradle | 8 +- .../net/corda/bank/BankOfCordaDriver.kt | 6 +- .../corda/bank/api/BankOfCordaClientApi.kt | 2 +- .../views/cordapps/cash/NewTransaction.kt | 17 +- .../net/corda/loadtest/tests/CrossCashTest.kt | 17 +- .../corda/loadtest/tests/GenerateHelpers.kt | 14 +- .../net/corda/loadtest/tests/SelfIssueTest.kt | 5 +- 25 files changed, 357 insertions(+), 254 deletions(-) create mode 100644 finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt create mode 100644 finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt create mode 100644 finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt create mode 100644 finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt diff --git a/client/src/integration-test/kotlin/net/corda/client/CordaRPCClientTest.kt b/client/src/integration-test/kotlin/net/corda/client/CordaRPCClientTest.kt index f4aaac98a8..10cb50a950 100644 --- a/client/src/integration-test/kotlin/net/corda/client/CordaRPCClientTest.kt +++ b/client/src/integration-test/kotlin/net/corda/client/CordaRPCClientTest.kt @@ -8,8 +8,8 @@ import net.corda.core.messaging.startFlow import net.corda.core.node.services.ServiceInfo import net.corda.core.random63BitValue import net.corda.core.serialization.OpaqueBytes -import net.corda.flows.CashCommand -import net.corda.flows.CashFlow +import net.corda.flows.CashIssueFlow +import net.corda.flows.CashPaymentFlow import net.corda.node.internal.Node import net.corda.node.services.User import net.corda.node.services.config.configureTestSSL @@ -23,7 +23,10 @@ import org.junit.Before import org.junit.Test class CordaRPCClientTest : NodeBasedTest() { - private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission())) + private val rpcUser = User("user1", "test", permissions = setOf( + startFlowPermission(), + startFlowPermission() + )) private lateinit var node: Node private lateinit var client: CordaRPCClient @@ -60,8 +63,8 @@ class CordaRPCClientTest : NodeBasedTest() { val proxy = client.proxy() println("Starting flow") val flowHandle = proxy.startFlow( - ::CashFlow, - CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity, node.info.legalIdentity)) + ::CashIssueFlow, + 20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity, node.info.legalIdentity) println("Started flow, waiting on result") flowHandle.progress.subscribe { println("PROGRESS $it") @@ -73,10 +76,10 @@ class CordaRPCClientTest : NodeBasedTest() { fun `FlowException thrown by flow`() { client.start(rpcUser.username, rpcUser.password) val proxy = client.proxy() - val handle = proxy.startFlow(::CashFlow, CashCommand.PayCash( - amount = 100.DOLLARS.issuedBy(node.info.legalIdentity.ref(1)), - recipient = node.info.legalIdentity - )) + val handle = proxy.startFlow(::CashPaymentFlow, + 100.DOLLARS.issuedBy(node.info.legalIdentity.ref(1)), + node.info.legalIdentity + ) // TODO Restrict this to CashException once RPC serialisation has been fixed assertThatExceptionOfType(FlowException::class.java).isThrownBy { handle.returnValue.getOrThrow() diff --git a/client/src/integration-test/kotlin/net/corda/client/NodeMonitorModelTest.kt b/client/src/integration-test/kotlin/net/corda/client/NodeMonitorModelTest.kt index 12eab3fb5e..4735014709 100644 --- a/client/src/integration-test/kotlin/net/corda/client/NodeMonitorModelTest.kt +++ b/client/src/integration-test/kotlin/net/corda/client/NodeMonitorModelTest.kt @@ -19,7 +19,6 @@ import net.corda.core.node.services.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.serialization.OpaqueBytes import net.corda.core.transactions.SignedTransaction -import net.corda.flows.CashCommand import net.corda.flows.CashFlow import net.corda.node.driver.DriverBasedTest import net.corda.node.driver.driver @@ -94,7 +93,7 @@ class NodeMonitorModelTest : DriverBasedTest() { @Test fun `cash issue works end to end`() { - rpc.startFlow(::CashFlow, CashCommand.IssueCash( + rpc.startFlow(::CashFlow, CashFlow.Command.IssueCash( amount = Amount(100, USD), issueRef = OpaqueBytes(ByteArray(1, { 1 })), recipient = aliceNode.legalIdentity, @@ -119,14 +118,14 @@ class NodeMonitorModelTest : DriverBasedTest() { @Test fun `cash issue and move`() { - rpc.startFlow(::CashFlow, CashCommand.IssueCash( + rpc.startFlow(::CashFlow, CashFlow.Command.IssueCash( amount = Amount(100, USD), issueRef = OpaqueBytes(ByteArray(1, { 1 })), recipient = aliceNode.legalIdentity, notary = notaryNode.notaryIdentity )).returnValue.getOrThrow() - rpc.startFlow(::CashFlow, CashCommand.PayCash( + rpc.startFlow(::CashFlow, CashFlow.Command.PayCash( amount = Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)), recipient = aliceNode.legalIdentity )) diff --git a/client/src/main/kotlin/net/corda/client/mock/EventGenerator.kt b/client/src/main/kotlin/net/corda/client/mock/EventGenerator.kt index 487bde0c56..1eb97c5963 100644 --- a/client/src/main/kotlin/net/corda/client/mock/EventGenerator.kt +++ b/client/src/main/kotlin/net/corda/client/mock/EventGenerator.kt @@ -5,7 +5,7 @@ import net.corda.core.contracts.* import net.corda.core.crypto.Party import net.corda.core.serialization.OpaqueBytes import net.corda.core.transactions.TransactionBuilder -import net.corda.flows.CashCommand +import net.corda.flows.CashFlow import java.util.* /** @@ -64,7 +64,7 @@ class EventGenerator( val issueCashGenerator = amountGenerator.combine(partyGenerator, issueRefGenerator) { amount, to, issueRef -> - CashCommand.IssueCash( + CashFlow.Command.IssueCash( amount, issueRef, to, @@ -76,7 +76,7 @@ class EventGenerator( amountIssuedGenerator.combine( partyGenerator ) { amountIssued, recipient -> - CashCommand.PayCash( + CashFlow.Command.PayCash( amount = amountIssued, recipient = recipient ) @@ -84,7 +84,7 @@ class EventGenerator( val exitCashGenerator = amountIssuedGenerator.map { - CashCommand.ExitCash( + CashFlow.Command.ExitCash( it.withoutIssuer(), it.token.issuer.reference ) 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 d08bcf7f76..123c473e64 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 @@ -9,7 +9,7 @@ import net.corda.core.messaging.startFlow import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.Vault import net.corda.core.serialization.OpaqueBytes -import net.corda.flows.CashCommand +import net.corda.core.toFuture import net.corda.flows.CashFlow import net.corda.node.driver.driver import net.corda.node.services.User @@ -56,7 +56,7 @@ class IntegrationTestingTutorial { val issueRef = OpaqueBytes.of(0) for (i in 1 .. 10) { thread { - aliceProxy.startFlow(::CashFlow, CashCommand.IssueCash( + aliceProxy.startFlow(::CashFlow, CashFlow.Command.IssueCash( amount = i.DOLLARS, issueRef = issueRef, recipient = bob.nodeInfo.legalIdentity, @@ -82,7 +82,7 @@ class IntegrationTestingTutorial { // START 5 for (i in 1 .. 10) { - val flowHandle = bobProxy.startFlow(::CashFlow, CashCommand.PayCash( + val flowHandle = bobProxy.startFlow(::CashFlow, CashFlow.Command.PayCash( amount = i.DOLLARS.issuedBy(alice.nodeInfo.legalIdentity.ref(issueRef)), recipient = alice.nodeInfo.legalIdentity )) @@ -102,4 +102,4 @@ class IntegrationTestingTutorial { } } } -// END 5 \ No newline at end of file +// END 5 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 b7e2430e54..b61c849b56 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 @@ -12,8 +12,9 @@ import net.corda.core.node.CordaPluginRegistry import net.corda.core.node.services.ServiceInfo import net.corda.core.serialization.OpaqueBytes import net.corda.core.transactions.SignedTransaction -import net.corda.flows.CashCommand -import net.corda.flows.CashFlow +import net.corda.flows.CashExitFlow +import net.corda.flows.CashIssueFlow +import net.corda.flows.CashPaymentFlow import net.corda.node.driver.driver import net.corda.node.services.User import net.corda.node.services.startFlowPermission @@ -41,7 +42,9 @@ fun main(args: Array) { val printOrVisualise = PrintOrVisualise.valueOf(args[0]) val baseDirectory = Paths.get("build/rpc-api-tutorial") - val user = User("user", "password", permissions = setOf(startFlowPermission())) + val user = User("user", "password", permissions = setOf(startFlowPermission(), + startFlowPermission(), + startFlowPermission())) driver(driverDirectory = baseDirectory) { startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))) @@ -114,14 +117,14 @@ fun generateTransactions(proxy: CordaRPCOps) { val n = random.nextDouble() if (ownedQuantity > 10000 && n > 0.8) { val quantity = Math.abs(random.nextLong()) % 2000 - proxy.startFlow(::CashFlow, CashCommand.ExitCash(Amount(quantity, USD), issueRef)) + proxy.startFlow(::CashExitFlow, Amount(quantity, USD), issueRef) ownedQuantity -= quantity } else if (ownedQuantity > 1000 && n < 0.7) { val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000)) - proxy.startFlow(::CashFlow, CashCommand.PayCash(Amount(quantity, Issued(meAndRef, USD)), me)) + proxy.startFlow(::CashPaymentFlow, Amount(quantity, Issued(meAndRef, USD)), me) } else { val quantity = Math.abs(random.nextLong() % 1000) - proxy.startFlow(::CashFlow, CashCommand.IssueCash(Amount(quantity, USD), issueRef, me, notary)) + proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, me, notary) ownedQuantity += quantity } } 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 9a1cba3b93..ce41490c63 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 @@ -1,6 +1,5 @@ package net.corda.docs -import net.corda.core.crypto.Party import net.corda.core.contracts.* import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo @@ -8,9 +7,8 @@ import net.corda.core.serialization.OpaqueBytes import net.corda.core.toFuture import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY_KEY -import net.corda.flows.CashCommand -import net.corda.flows.CashFlow -import net.corda.core.node.ServiceEntry +import net.corda.flows.CashIssueFlow +import net.corda.flows.CashPaymentFlow import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.utilities.databaseTransaction @@ -51,19 +49,19 @@ class FxTransactionBuildTutorialTest { @Test fun `Run ForeignExchangeFlow to completion`() { // Use NodeA as issuer and create some dollars - val flowHandle1 = nodeA.services.startFlow(CashFlow(CashCommand.IssueCash(DOLLARS(1000), + val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(DOLLARS(1000), OpaqueBytes.of(0x01), nodeA.info.legalIdentity, - notaryNode.info.notaryIdentity))) + notaryNode.info.notaryIdentity)) // Wait for the flow to stop and print flowHandle1.resultFuture.getOrThrow() printBalances() // Using NodeB as Issuer create some pounds. - val flowHandle2 = nodeB.services.startFlow(CashFlow(CashCommand.IssueCash(POUNDS(1000), + val flowHandle2 = nodeB.services.startFlow(CashIssueFlow(POUNDS(1000), OpaqueBytes.of(0x01), nodeB.info.legalIdentity, - notaryNode.info.notaryIdentity))) + notaryNode.info.notaryIdentity)) // Wait for flow to come to an end and print flowHandle2.resultFuture.getOrThrow() printBalances() @@ -107,4 +105,4 @@ class FxTransactionBuildTutorialTest { println("BalanceB\n" + nodeB.services.vaultService.cashBalances) } } -} \ No newline at end of file +} diff --git a/docs/source/running-the-demos.rst b/docs/source/running-the-demos.rst index 73d6124a98..d51f5e779d 100644 --- a/docs/source/running-the-demos.rst +++ b/docs/source/running-the-demos.rst @@ -259,8 +259,8 @@ Launch the Explorer application to visualize the issuance and transfer of cash f Using the following login details: -- For the Bank of Corda node: localhost / port 10004 / username user1 / password test -- For the Big Corporation node: localhost / port 10006 / username user1 / password test +- For the Bank of Corda node: localhost / port 10004 / username bankUser / password test +- For the Big Corporation node: localhost / port 10006 / username bigCorpUser / password test See https://docs.corda.net/node-explorer.html for further details on usage. diff --git a/finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt b/finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt new file mode 100644 index 0000000000..698b5fa188 --- /dev/null +++ b/finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt @@ -0,0 +1,32 @@ +package net.corda.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.crypto.Party +import net.corda.core.flows.FlowException +import net.corda.core.flows.FlowLogic +import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.ProgressTracker + +/** + * Initiates a flow that produces an Issue/Move or Exit Cash transaction. + */ +abstract class AbstractCashFlow(override val progressTracker: ProgressTracker) : FlowLogic() { + companion object { + object GENERATING_TX : ProgressTracker.Step("Generating transaction") + object SIGNING_TX : ProgressTracker.Step("Signing transaction") + object FINALISING_TX : ProgressTracker.Step("Finalising transaction") + + fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX) + } + + @Suspendable + internal fun finaliseTx(participants: Set, tx: SignedTransaction, message: String) { + try { + subFlow(FinalityFlow(tx, participants)) + } catch (e: NotaryException) { + throw CashException(message, e) + } + } +} + +class CashException(message: String, cause: Throwable) : FlowException(message, cause) \ No newline at end of file diff --git a/finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt new file mode 100644 index 0000000000..de495bb9eb --- /dev/null +++ b/finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt @@ -0,0 +1,67 @@ +package net.corda.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.* +import net.corda.core.crypto.Party +import net.corda.core.serialization.OpaqueBytes +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.ProgressTracker +import java.util.* + +/** + * Initiates a flow that produces an cash exit transaction. + * + * @param amount the amount of a currency to remove from the ledger. + * @param issuerRef the reference on the issued currency. Added to the node's legal identity to determine the + * issuer. + */ +class CashExitFlow(val amount: Amount, val issueRef: OpaqueBytes, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { + constructor(amount: Amount, issueRef: OpaqueBytes) : this(amount, issueRef, tracker()) + + companion object { + fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX) + } + + @Suspendable + @Throws(CashException::class) + override fun call(): SignedTransaction { + progressTracker.currentStep = GENERATING_TX + val builder: TransactionBuilder = TransactionType.General.Builder(null) + val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef) + try { + Cash().generateExit( + builder, + amount.issuedBy(issuer), + serviceHub.vaultService.currentVault.statesOfType().filter { it.state.data.owner == issuer.party.owningKey }) + } catch (e: InsufficientBalanceException) { + throw CashException("Exiting more cash than exists", e) + } + progressTracker.currentStep = SIGNING_TX + val myKey = serviceHub.legalIdentityKey + builder.signWith(myKey) + + // Work out who the owners of the burnt states were + val inputStatesNullable = serviceHub.vaultService.statesForRefs(builder.inputStates()) + val inputStates = inputStatesNullable.values.filterNotNull().map { it.data } + if (inputStatesNullable.size != inputStates.size) { + val unresolvedStateRefs = inputStatesNullable.filter { it.value == null }.map { it.key } + throw IllegalStateException("Failed to resolve input StateRefs: $unresolvedStateRefs") + } + + // TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them + // count as a reason to fail? + val participants: Set = inputStates + .filterIsInstance() + .map { serviceHub.identityService.partyFromKey(it.owner) } + .filterNotNull() + .toSet() + + // Commit the transaction + val tx = builder.toSignedTransaction(checkSufficientSignatures = false) + progressTracker.currentStep = FINALISING_TX + finaliseTx(participants, tx, "Unable to notarise exit") + return tx + } +} diff --git a/finance/src/main/kotlin/net/corda/flows/CashFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashFlow.kt index cc7d7331fc..2c921ee768 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashFlow.kt @@ -1,19 +1,13 @@ package net.corda.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.contracts.asset.Cash -import net.corda.core.contracts.* -import net.corda.core.crypto.AnonymousParty +import net.corda.core.contracts.Amount +import net.corda.core.contracts.Issued import net.corda.core.crypto.Party -import net.corda.core.crypto.keys -import net.corda.core.crypto.toStringShort -import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic import net.corda.core.serialization.OpaqueBytes import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker -import java.security.KeyPair import java.util.* /** @@ -21,8 +15,8 @@ import java.util.* * * @param command Indicates what Cash transaction to create with what parameters. */ -class CashFlow(val command: CashCommand, override val progressTracker: ProgressTracker) : FlowLogic() { - constructor(command: CashCommand) : this(command, tracker()) +class CashFlow(val command: CashFlow.Command, override val progressTracker: ProgressTracker) : FlowLogic() { + constructor(command: CashFlow.Command) : this(command, tracker()) companion object { object ISSUING : ProgressTracker.Step("Issuing cash") @@ -36,133 +30,38 @@ class CashFlow(val command: CashCommand, override val progressTracker: ProgressT @Throws(CashException::class) override fun call(): SignedTransaction { return when (command) { - is CashCommand.IssueCash -> issueCash(command) - is CashCommand.PayCash -> initiatePayment(command) - is CashCommand.ExitCash -> exitCash(command) + is CashFlow.Command.IssueCash -> subFlow(CashIssueFlow(command.amount, command.issueRef, command.recipient, command.notary)) + is CashFlow.Command.PayCash -> subFlow(CashPaymentFlow(command.amount, command.recipient)) + is CashFlow.Command.ExitCash -> subFlow(CashExitFlow(command.amount, command.issueRef)) } } - // TODO check with the recipient if they want to accept the cash. - @Suspendable - private fun initiatePayment(req: CashCommand.PayCash): SignedTransaction { - progressTracker.currentStep = PAYING - val builder: TransactionBuilder = TransactionType.General.Builder(null) - // TODO: Have some way of restricting this to states the caller controls - val (spendTX, keysForSigning) = try { - serviceHub.vaultService.generateSpend( - builder, - req.amount.withoutIssuer(), - req.recipient.owningKey, - setOf(req.amount.token.issuer.party)) - } catch (e: InsufficientBalanceException) { - throw CashException("Insufficent cash for spend", e) - } + /** + * A command to initiate the Cash flow with. + */ + sealed class Command { + /** + * A command to initiate the Cash flow with. + */ + class IssueCash(val amount: Amount, + val issueRef: OpaqueBytes, + val recipient: Party, + val notary: Party) : CashFlow.Command() - keysForSigning.keys.forEach { - val key = serviceHub.keyManagementService.keys[it] ?: throw IllegalStateException("Could not find signing key for ${it.toStringShort()}") - builder.signWith(KeyPair(it, key)) - } + /** + * Pay cash to someone else. + * + * @param amount the amount of currency to issue on to the ledger. + * @param recipient the party to issue the cash to. + */ + class PayCash(val amount: Amount>, val recipient: Party) : CashFlow.Command() - val tx = spendTX.toSignedTransaction(checkSufficientSignatures = false) - finaliseTx(setOf(req.recipient), tx, "Unable to notarise spend") - return tx - } - - @Suspendable - private fun exitCash(req: CashCommand.ExitCash): SignedTransaction { - progressTracker.currentStep = EXITING - val builder: TransactionBuilder = TransactionType.General.Builder(null) - val issuer = serviceHub.myInfo.legalIdentity.ref(req.issueRef) - try { - Cash().generateExit( - builder, - req.amount.issuedBy(issuer), - serviceHub.vaultService.currentVault.statesOfType().filter { it.state.data.owner == issuer.party.owningKey }) - } catch (e: InsufficientBalanceException) { - throw CashException("Exiting more cash than exists", e) - } - val myKey = serviceHub.legalIdentityKey - builder.signWith(myKey) - - // Work out who the owners of the burnt states were - val inputStatesNullable = serviceHub.vaultService.statesForRefs(builder.inputStates()) - val inputStates = inputStatesNullable.values.filterNotNull().map { it.data } - if (inputStatesNullable.size != inputStates.size) { - val unresolvedStateRefs = inputStatesNullable.filter { it.value == null }.map { it.key } - throw IllegalStateException("Failed to resolve input StateRefs: $unresolvedStateRefs") - } - - // TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them - // count as a reason to fail? - val participants: Set = inputStates - .filterIsInstance() - .map { serviceHub.identityService.partyFromKey(it.owner) } - .filterNotNull() - .toSet() - - // Commit the transaction - val tx = builder.toSignedTransaction(checkSufficientSignatures = false) - finaliseTx(participants, tx, "Unable to notarise exit") - return tx - } - - @Suspendable - private fun finaliseTx(participants: Set, tx: SignedTransaction, message: String) { - try { - subFlow(FinalityFlow(tx, participants)) - } catch (e: NotaryException) { - throw CashException(message, e) - } - } - - // TODO This doesn't throw any exception so it might be worth splitting the three cash commands into separate flows - @Suspendable - private fun issueCash(req: CashCommand.IssueCash): SignedTransaction { - progressTracker.currentStep = ISSUING - val builder: TransactionBuilder = TransactionType.General.Builder(notary = null) - val issuer = serviceHub.myInfo.legalIdentity.ref(req.issueRef) - Cash().generateIssue(builder, req.amount.issuedBy(issuer), req.recipient.owningKey, req.notary) - val myKey = serviceHub.legalIdentityKey - builder.signWith(myKey) - val tx = builder.toSignedTransaction() - subFlow(FinalityFlow(tx)) - return tx + /** + * Exit cash from the ledger. + * + * @param amount the amount of currency to exit from the ledger. + * @param issueRef the reference previously specified on the issuance. + */ + class ExitCash(val amount: Amount, val issueRef: OpaqueBytes) : CashFlow.Command() } } - -/** - * A command to initiate the Cash flow with. - */ -sealed class CashCommand { - /** - * Issue cash state objects. - * - * @param amount the amount of currency to issue on to the ledger. - * @param issueRef the reference to specify on the issuance, used to differentiate pools of cash. Convention is - * to use the single byte "0x01" as a default. - * @param recipient the party to issue the cash to. - * @param notary the notary to use for this transaction. - */ - class IssueCash(val amount: Amount, - val issueRef: OpaqueBytes, - val recipient: Party, - val notary: Party) : CashCommand() - - /** - * Pay cash to someone else. - * - * @param amount the amount of currency to issue on to the ledger. - * @param recipient the party to issue the cash to. - */ - class PayCash(val amount: Amount>, val recipient: Party) : CashCommand() - - /** - * Exit cash from the ledger. - * - * @param amount the amount of currency to exit from the ledger. - * @param issueRef the reference previously specified on the issuance. - */ - class ExitCash(val amount: Amount, val issueRef: OpaqueBytes) : CashCommand() -} - -class CashException(message: String, cause: Throwable) : FlowException(message, cause) diff --git a/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt new file mode 100644 index 0000000000..f36006acc6 --- /dev/null +++ b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt @@ -0,0 +1,46 @@ +package net.corda.flows + +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.Amount +import net.corda.core.contracts.PartyAndReference +import net.corda.core.contracts.TransactionType +import net.corda.core.contracts.issuedBy +import net.corda.core.crypto.Party +import net.corda.core.serialization.OpaqueBytes +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.ProgressTracker +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 notary the notary to set on the output states. + */ +class CashIssueFlow(val amount: Amount, + val issueRef: OpaqueBytes, + val recipient: Party, + val notary: Party, + progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { + constructor(amount: Amount, + issueRef: OpaqueBytes, + recipient: Party, + notary: Party) : this(amount, issueRef, recipient, notary, tracker()) + + override fun call(): SignedTransaction { + progressTracker.currentStep = GENERATING_TX + val builder: TransactionBuilder = TransactionType.General.Builder(notary = null) + val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef) + Cash().generateIssue(builder, amount.issuedBy(issuer), recipient.owningKey, notary) + progressTracker.currentStep = SIGNING_TX + val myKey = serviceHub.legalIdentityKey + builder.signWith(myKey) + val tx = builder.toSignedTransaction() + progressTracker.currentStep = FINALISING_TX + subFlow(FinalityFlow(tx)) + return tx + } +} diff --git a/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt new file mode 100644 index 0000000000..7c0100dd88 --- /dev/null +++ b/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt @@ -0,0 +1,49 @@ +package net.corda.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.* +import net.corda.core.crypto.Party +import net.corda.core.crypto.keys +import net.corda.core.crypto.toStringShort +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.ProgressTracker +import java.security.KeyPair +import java.util.* + +/** + * Initiates a flow that produces an cash move transaction. + * + * @param amount the amount of a currency to pay to the recipient. + * @param recipient the party to pay the currency to. + */ +open class CashPaymentFlow(val amount: Amount>, val recipient: Party, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { + constructor(amount: Amount>, recipient: Party) : this(amount, recipient, tracker()) + + @Suspendable + override fun call(): SignedTransaction { + progressTracker.currentStep = GENERATING_TX + val builder: TransactionBuilder = TransactionType.General.Builder(null) + // TODO: Have some way of restricting this to states the caller controls + val (spendTX, keysForSigning) = try { + serviceHub.vaultService.generateSpend( + builder, + amount.withoutIssuer(), + recipient.owningKey, + setOf(amount.token.issuer.party)) + } catch (e: InsufficientBalanceException) { + throw CashException("Insufficent cash for spend", e) + } + + progressTracker.currentStep = SIGNING_TX + keysForSigning.keys.forEach { + val key = serviceHub.keyManagementService.keys[it] ?: throw IllegalStateException("Could not find signing key for ${it.toStringShort()}") + builder.signWith(KeyPair(it, key)) + } + + progressTracker.currentStep = FINALISING_TX + val tx = spendTX.toSignedTransaction(checkSufficientSignatures = false) + finaliseTx(setOf(recipient), tx, "Unable to notarise spend") + return tx + } +} \ No newline at end of file diff --git a/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt b/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt index 921a6120eb..671ac53340 100644 --- a/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt @@ -79,7 +79,7 @@ object IssuerFlow { // invoke Cash subflow to issue Asset progressTracker.currentStep = ISSUING val bankOfCordaParty = serviceHub.myInfo.legalIdentity - val issueCashFlow = CashFlow(CashCommand.IssueCash(amount, issuerPartyRef, bankOfCordaParty, notaryParty)) + val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, bankOfCordaParty, notaryParty) 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 @@ -87,7 +87,7 @@ object IssuerFlow { return issueTx // now invoke Cash subflow to Move issued assetType to issue requester progressTracker.currentStep = TRANSFERRING - val moveCashFlow = CashFlow(CashCommand.PayCash(amount.issuedBy(bankOfCordaParty.ref(issuerPartyRef)), issueTo)) + val moveCashFlow = CashPaymentFlow(amount.issuedBy(bankOfCordaParty.ref(issuerPartyRef)), issueTo) 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 diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 03461b23be..d798811556 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -11,8 +11,9 @@ import net.corda.core.messaging.StateMachineUpdate import net.corda.core.messaging.startFlow import net.corda.core.node.NodeInfo import net.corda.core.serialization.OpaqueBytes -import net.corda.flows.CashCommand -import net.corda.flows.CashFlow +import net.corda.core.transactions.SignedTransaction +import net.corda.flows.CashIssueFlow +import net.corda.flows.CashPaymentFlow import net.corda.node.driver.DriverBasedTest import net.corda.node.driver.NodeHandle import net.corda.node.driver.driver @@ -35,7 +36,10 @@ class DistributedServiceTests : DriverBasedTest() { override fun setup() = driver { // Start Alice and 3 notaries in a RAFT cluster val clusterSize = 3 - val testUser = User("test", "test", permissions = setOf(startFlowPermission())) + val testUser = User("test", "test", permissions = setOf( + startFlowPermission(), + startFlowPermission()) + ) val aliceFuture = startNode("Alice", rpcUsers = listOf(testUser)) val notariesFuture = startNotaryCluster( "Notary", @@ -135,15 +139,15 @@ class DistributedServiceTests : DriverBasedTest() { private fun issueCash(amount: Amount) { val issueHandle = aliceProxy.startFlow( - ::CashFlow, - CashCommand.IssueCash(amount, OpaqueBytes.of(0), alice.nodeInfo.legalIdentity, raftNotaryIdentity)) + ::CashIssueFlow, + amount, OpaqueBytes.of(0), alice.nodeInfo.legalIdentity, raftNotaryIdentity) issueHandle.returnValue.getOrThrow() } private fun paySelf(amount: Amount) { val payHandle = aliceProxy.startFlow( - ::CashFlow, - CashCommand.PayCash(amount.issuedBy(alice.nodeInfo.legalIdentity.ref(0)), alice.nodeInfo.legalIdentity)) + ::CashPaymentFlow, + amount.issuedBy(alice.nodeInfo.legalIdentity.ref(0)), alice.nodeInfo.legalIdentity) payHandle.returnValue.getOrThrow() } -} \ 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 126f53e23f..b5a79865c5 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -6,6 +6,8 @@ import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.SettableFuture import net.corda.core.* +import net.corda.core.contracts.Amount +import net.corda.core.contracts.PartyAndReference import net.corda.core.crypto.Party import net.corda.core.crypto.X509Utilities import net.corda.core.flows.FlowLogic @@ -16,14 +18,12 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.* import net.corda.core.node.services.* import net.corda.core.node.services.NetworkMapCache.MapChange +import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction -import net.corda.flows.CashCommand -import net.corda.flows.CashFlow -import net.corda.flows.FinalityFlow -import net.corda.flows.sendRequest +import net.corda.flows.* import net.corda.node.services.api.* import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate @@ -51,7 +51,6 @@ import net.corda.node.utilities.databaseTransaction import org.apache.activemq.artemis.utils.ReusableLatch import org.jetbrains.exposed.sql.Database import org.slf4j.Logger -import java.io.File import java.nio.file.FileAlreadyExistsException import java.nio.file.Path import java.security.KeyPair @@ -82,11 +81,12 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val PUBLIC_IDENTITY_FILE_NAME = "identity-public" val defaultFlowWhiteList: Map>, Set>> = mapOf( - CashFlow::class.java to setOf( - CashCommand.IssueCash::class.java, - CashCommand.PayCash::class.java, - CashCommand.ExitCash::class.java - ), + CashFlow::class.java to setOf(CashFlow.Command.IssueCash::class.java, + CashFlow.Command.PayCash::class.java, + CashFlow.Command.ExitCash::class.java), + CashExitFlow::class.java to setOf(Amount::class.java, PartyAndReference::class.java), + CashIssueFlow::class.java to setOf(Amount::class.java, OpaqueBytes::class.java, Party::class.java), + CashPaymentFlow::class.java to setOf(Amount::class.java, Party::class.java), FinalityFlow::class.java to emptySet() ) } diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 170840485f..634a2855ac 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -9,8 +9,8 @@ import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.Vault import net.corda.core.serialization.OpaqueBytes import net.corda.core.transactions.SignedTransaction -import net.corda.flows.CashCommand -import net.corda.flows.CashFlow +import net.corda.flows.CashIssueFlow +import net.corda.flows.CashPaymentFlow import net.corda.node.internal.CordaRPCOpsImpl import net.corda.node.services.User import net.corda.node.services.messaging.CURRENT_RPC_USER @@ -48,7 +48,10 @@ class CordaRPCOpsImplTest { aliceNode = network.createNode(networkMapAddress = networkMap.info.address) notaryNode = network.createNode(advertisedServices = ServiceInfo(SimpleNotaryService.type), networkMapAddress = networkMap.info.address) rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database) - CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(startFlowPermission()))) + CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf( + startFlowPermission(), + startFlowPermission() + ))) databaseTransaction(aliceNode.database) { stateMachineUpdates = rpc.stateMachinesAndUpdates().second @@ -69,8 +72,7 @@ class CordaRPCOpsImplTest { // Tell the monitoring service node to issue some cash val recipient = aliceNode.info.legalIdentity - val outEvent = CashCommand.IssueCash(Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity) - rpc.startFlow(::CashFlow, outEvent) + rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity) network.runNetwork() val expectedState = Cash.State(Amount(quantity, @@ -107,19 +109,19 @@ class CordaRPCOpsImplTest { @Test fun `issue and move`() { - rpc.startFlow(::CashFlow, CashCommand.IssueCash( - amount = Amount(100, USD), - issueRef = OpaqueBytes(ByteArray(1, { 1 })), - recipient = aliceNode.info.legalIdentity, - notary = notaryNode.info.notaryIdentity - )) + rpc.startFlow(::CashIssueFlow, + Amount(100, USD), + OpaqueBytes(ByteArray(1, { 1 })), + aliceNode.info.legalIdentity, + notaryNode.info.notaryIdentity + ) network.runNetwork() - rpc.startFlow(::CashFlow, CashCommand.PayCash( - amount = Amount(100, Issued(PartyAndReference(aliceNode.info.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)), - recipient = aliceNode.info.legalIdentity - )) + rpc.startFlow(::CashPaymentFlow, + Amount(100, Issued(PartyAndReference(aliceNode.info.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)), + aliceNode.info.legalIdentity + ) network.runNetwork() @@ -188,12 +190,12 @@ class CordaRPCOpsImplTest { fun `cash command by user not permissioned for cash`() { CURRENT_RPC_USER.set(User("user", "pwd", permissions = emptySet())) assertThatExceptionOfType(PermissionException::class.java).isThrownBy { - rpc.startFlow(::CashFlow, CashCommand.IssueCash( - amount = Amount(100, USD), - issueRef = OpaqueBytes(ByteArray(1, { 1 })), - recipient = aliceNode.info.legalIdentity, - notary = notaryNode.info.notaryIdentity - )) + rpc.startFlow(::CashIssueFlow, + Amount(100, USD), + OpaqueBytes(ByteArray(1, { 1 })), + aliceNode.info.legalIdentity, + notaryNode.info.notaryIdentity + ) } } } diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt index ce06994a5d..f7a26dfcf7 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt @@ -24,8 +24,8 @@ import net.corda.core.serialization.deserialize import net.corda.core.utilities.unwrap import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.flows.CashCommand -import net.corda.flows.CashFlow +import net.corda.flows.CashIssueFlow +import net.corda.flows.CashPaymentFlow import net.corda.flows.FinalityFlow import net.corda.flows.NotaryFlow import net.corda.node.services.persistence.checkpoints @@ -320,16 +320,16 @@ class StateMachineManagerTests { @Test fun `different notaries are picked when addressing shared notary identity`() { assertEquals(notary1.info.notaryIdentity, notary2.info.notaryIdentity) - node1.services.startFlow(CashFlow(CashCommand.IssueCash( + node1.services.startFlow(CashIssueFlow( DOLLARS(2000), OpaqueBytes.of(0x01), node1.info.legalIdentity, - notary1.info.notaryIdentity))) + notary1.info.notaryIdentity)) // We pay a couple of times, the notary picking should go round robin for (i in 1 .. 3) { - node1.services.startFlow(CashFlow(CashCommand.PayCash( + node1.services.startFlow(CashPaymentFlow( DOLLARS(500).issuedBy(node1.info.legalIdentity.ref(0x01)), - node2.info.legalIdentity))) + node2.info.legalIdentity)) net.runNetwork() } val endpoint = net.messagingNetwork.endpoint(notary1.net.myAddress as InMemoryMessagingNetwork.PeerHandle)!! diff --git a/samples/bank-of-corda-demo/README.md b/samples/bank-of-corda-demo/README.md index 3d7aeda9a1..4c81eec7f9 100644 --- a/samples/bank-of-corda-demo/README.md +++ b/samples/bank-of-corda-demo/README.md @@ -41,9 +41,9 @@ Testing of the Bank of Corda application is demonstrated at two levels: Security The RPC API requires a client to pass in user credentials: - client.start("user1","test") + client.start("bankUser","test") which are validated on the Bank of Corda node against those configured at node startup: - User("user1", "test", permissions = setOf(startFlowPermission())) + User("bankUser", "test", permissions = setOf(startFlowPermission())) startNode("BankOfCorda", rpcUsers = listOf(user)) Notary diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index 53c006ae26..806ba0417f 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -77,9 +77,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) { webPort 10005 cordapps = [] rpcUsers = [ - ['user' : "user1", + ['user' : "bankUser", 'password' : "test", - 'permissions' : ["StartFlow.net.corda.flows.CashFlow", + 'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow", "StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]] ] } @@ -91,9 +91,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) { webPort 10007 cordapps = [] rpcUsers = [ - ['user' : "user1", + ['user' : "bigCorpUser", 'password' : "test", - 'permissions' : ["StartFlow.net.corda.flows.CashFlow"]] + 'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow"]] ] } } 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 1bfd3b874c..fdc003a3fb 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,7 +8,7 @@ import net.corda.flows.IssuerFlow import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceType import net.corda.core.transactions.SignedTransaction -import net.corda.flows.CashFlow +import net.corda.flows.CashPaymentFlow import net.corda.node.driver.driver import net.corda.node.services.User import net.corda.node.services.startFlowPermission @@ -51,8 +51,8 @@ private class BankOfCordaDriver { val role = options.valueOf(roleArg)!! if (role == Role.ISSUER) { driver(dsl = { - val bankUser = User(BANK_USERNAME, "test", permissions = setOf(startFlowPermission(), startFlowPermission())) - val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf(startFlowPermission())) + val bankUser = User(BANK_USERNAME, "test", permissions = setOf(startFlowPermission(), startFlowPermission())) + val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf(startFlowPermission())) startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type))) val bankOfCorda = startNode("BankOfCorda", rpcUsers = listOf(bankUser), advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.USD")))) startNode("BigCorporation", rpcUsers = listOf(bigCorpUser)) 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 3baac0202f..5d4b01a4f2 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 @@ -32,7 +32,7 @@ class BankOfCordaClientApi(val hostAndPort: HostAndPort) { fun requestRPCIssue(params: IssueRequestParams): SignedTransaction { val client = CordaRPCClient(hostAndPort, configureTestSSL()) // TODO: privileged security controls required - client.start("user1", "test") + client.start("bankUser", "test") val proxy = client.proxy() // Resolve parties via RPC 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 64ee18f7ca..fafdb48dd6 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 @@ -28,7 +28,6 @@ import net.corda.explorer.model.ReportingCurrencyModel import net.corda.explorer.views.bigDecimalFormatter import net.corda.explorer.views.byteFormatter import net.corda.explorer.views.stringConverter -import net.corda.flows.CashCommand import net.corda.flows.CashFlow import net.corda.flows.IssuerFlow.IssuanceRequester import org.controlsfx.dialog.ExceptionDialog @@ -88,7 +87,7 @@ class NewTransaction : Fragment() { } dialog.show() runAsync { - val handle = if (it is CashCommand.IssueCash) { + val handle = if (it is CashFlow.Command.IssueCash) { myIdentity.value?.let { myIdentity -> rpcProxy.value!!.startFlow(::IssuanceRequester, it.amount, @@ -111,9 +110,9 @@ class NewTransaction : Fragment() { Alert.AlertType.ERROR to response.message } else { val type = when (command) { - is CashCommand.IssueCash -> "Cash Issued" - is CashCommand.ExitCash -> "Cash Exited" - is CashCommand.PayCash -> "Cash Paid" + is CashFlow.Command.IssueCash -> "Cash Issued" + is CashFlow.Command.ExitCash -> "Cash Exited" + is CashFlow.Command.PayCash -> "Cash Paid" } Alert.AlertType.INFORMATION to "$type \nTransaction ID : ${(response as SignedTransaction).id}" } @@ -128,7 +127,7 @@ class NewTransaction : Fragment() { } } - private fun dialog(window: Window) = Dialog().apply { + private fun dialog(window: Window) = Dialog().apply { dialogPane = root initOwner(window) setResultConverter { @@ -137,10 +136,10 @@ class NewTransaction : Fragment() { when (it) { executeButton -> when (transactionTypeCB.value) { CashTransaction.Issue -> { - CashCommand.IssueCash(Amount(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity) + CashFlow.Command.IssueCash(Amount(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity) } - CashTransaction.Pay -> CashCommand.PayCash(Amount(amount.value, Issued(PartyAndReference(issuerChoiceBox.value, issueRef), currencyChoiceBox.value)), partyBChoiceBox.value.legalIdentity) - CashTransaction.Exit -> CashCommand.ExitCash(Amount(amount.value, currencyChoiceBox.value), issueRef) + CashTransaction.Pay -> CashFlow.Command.PayCash(Amount(amount.value, Issued(PartyAndReference(issuerChoiceBox.value, issueRef), currencyChoiceBox.value)), partyBChoiceBox.value.legalIdentity) + CashTransaction.Exit -> CashFlow.Command.ExitCash(Amount(amount.value, currencyChoiceBox.value), issueRef) else -> null } else -> null diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt index 86f90fd09c..0dd578a95e 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt @@ -12,7 +12,8 @@ import net.corda.core.flows.FlowException import net.corda.core.getOrThrow import net.corda.core.messaging.startFlow import net.corda.core.serialization.OpaqueBytes -import net.corda.flows.CashCommand +import net.corda.core.toFuture +import net.corda.flows.CashException import net.corda.flows.CashFlow import net.corda.loadtest.LoadTest import net.corda.loadtest.NodeHandle @@ -28,18 +29,18 @@ private val log = LoggerFactory.getLogger("CrossCash") */ data class CrossCashCommand( - val command: CashCommand, + val command: CashFlow.Command, val node: NodeHandle ) { override fun toString(): String { return when (command) { - is CashCommand.IssueCash -> { + is CashFlow.Command.IssueCash -> { "ISSUE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}" } - is CashCommand.PayCash -> { + is CashFlow.Command.PayCash -> { "MOVE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}" } - is CashCommand.ExitCash -> { + is CashFlow.Command.ExitCash -> { "EXIT ${node.info.legalIdentity} : ${command.amount}" } } @@ -145,7 +146,7 @@ val crossCashTest = LoadTest( interpret = { state, command -> when (command.command) { - is CashCommand.IssueCash -> { + is CashFlow.Command.IssueCash -> { val newDiffQueues = state.copyQueues() val originators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() }) val issuer = command.node.info.legalIdentity @@ -155,7 +156,7 @@ val crossCashTest = LoadTest( queue.add(Pair(issuer, quantity)) CrossCashState(state.nodeVaults, newDiffQueues) } - is CashCommand.PayCash -> { + is CashFlow.Command.PayCash -> { val newNodeVaults = state.copyVaults() val newDiffQueues = state.copyQueues() val recipientOriginators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() }) @@ -182,7 +183,7 @@ val crossCashTest = LoadTest( recipientQueue.add(Pair(issuer, quantity)) CrossCashState(newNodeVaults, newDiffQueues) } - is CashCommand.ExitCash -> { + is CashFlow.Command.ExitCash -> { val newNodeVaults = state.copyVaults() val issuer = command.node.info.legalIdentity val quantity = command.command.amount.quantity diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt index 8bce6439a9..ffeb83ea45 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt @@ -8,7 +8,7 @@ import net.corda.core.contracts.PartyAndReference import net.corda.core.crypto.AnonymousParty import net.corda.core.crypto.Party import net.corda.core.serialization.OpaqueBytes -import net.corda.flows.CashCommand +import net.corda.flows.CashFlow import java.util.* fun generateIssue( @@ -16,12 +16,12 @@ fun generateIssue( currency: Currency, notary: Party, possibleRecipients: List -): Generator { +): Generator { return generateAmount(0, max, Generator.pure(currency)).combine( Generator.pure(OpaqueBytes.of(0)), Generator.pickOne(possibleRecipients) ) { amount, ref, recipient -> - CashCommand.IssueCash(amount, ref, recipient, notary) + CashFlow.Command.IssueCash(amount, ref, recipient, notary) } } @@ -30,19 +30,19 @@ fun generateMove( currency: Currency, issuer: AnonymousParty, possibleRecipients: List -): Generator { +): Generator { return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine( Generator.pickOne(possibleRecipients) ) { amount, recipient -> - CashCommand.PayCash(amount, recipient) + CashFlow.Command.PayCash(amount, recipient) } } fun generateExit( max: Long, currency: Currency -): Generator { +): Generator { return generateAmount(1, max, Generator.pure(currency)).map { amount -> - CashCommand.ExitCash(amount, OpaqueBytes.of(0)) + CashFlow.Command.ExitCash(amount, OpaqueBytes.of(0)) } } diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt index c4e7106094..f957bb0d5c 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt @@ -11,7 +11,8 @@ import net.corda.core.crypto.Party import net.corda.core.flows.FlowException import net.corda.core.getOrThrow import net.corda.core.messaging.startFlow -import net.corda.flows.CashCommand +import net.corda.core.toFuture +import net.corda.flows.CashException import net.corda.flows.CashFlow import net.corda.loadtest.LoadTest import net.corda.loadtest.NodeHandle @@ -22,7 +23,7 @@ private val log = LoggerFactory.getLogger("SelfIssue") // DOCS START 1 data class SelfIssueCommand( - val command: CashCommand.IssueCash, + val command: CashFlow.Command.IssueCash, val node: NodeHandle )