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 <ross.nicoll@r3.com>
This commit is contained in:
Ross Nicoll 2017-01-30 18:34:48 +00:00
parent 47d260625a
commit 9055c9d9b0
25 changed files with 357 additions and 254 deletions

View File

@ -8,8 +8,8 @@ import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.random63BitValue import net.corda.core.random63BitValue
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashCommand import net.corda.flows.CashIssueFlow
import net.corda.flows.CashFlow import net.corda.flows.CashPaymentFlow
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL import net.corda.node.services.config.configureTestSSL
@ -23,7 +23,10 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
class CordaRPCClientTest : NodeBasedTest() { class CordaRPCClientTest : NodeBasedTest() {
private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>())) private val rpcUser = User("user1", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
))
private lateinit var node: Node private lateinit var node: Node
private lateinit var client: CordaRPCClient private lateinit var client: CordaRPCClient
@ -60,8 +63,8 @@ class CordaRPCClientTest : NodeBasedTest() {
val proxy = client.proxy() val proxy = client.proxy()
println("Starting flow") println("Starting flow")
val flowHandle = proxy.startFlow( val flowHandle = proxy.startFlow(
::CashFlow, ::CashIssueFlow,
CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity, node.info.legalIdentity)) 20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity, node.info.legalIdentity)
println("Started flow, waiting on result") println("Started flow, waiting on result")
flowHandle.progress.subscribe { flowHandle.progress.subscribe {
println("PROGRESS $it") println("PROGRESS $it")
@ -73,10 +76,10 @@ class CordaRPCClientTest : NodeBasedTest() {
fun `FlowException thrown by flow`() { fun `FlowException thrown by flow`() {
client.start(rpcUser.username, rpcUser.password) client.start(rpcUser.username, rpcUser.password)
val proxy = client.proxy() val proxy = client.proxy()
val handle = proxy.startFlow(::CashFlow, CashCommand.PayCash( val handle = proxy.startFlow(::CashPaymentFlow,
amount = 100.DOLLARS.issuedBy(node.info.legalIdentity.ref(1)), 100.DOLLARS.issuedBy(node.info.legalIdentity.ref(1)),
recipient = node.info.legalIdentity node.info.legalIdentity
)) )
// TODO Restrict this to CashException once RPC serialisation has been fixed // TODO Restrict this to CashException once RPC serialisation has been fixed
assertThatExceptionOfType(FlowException::class.java).isThrownBy { assertThatExceptionOfType(FlowException::class.java).isThrownBy {
handle.returnValue.getOrThrow() handle.returnValue.getOrThrow()

View File

@ -19,7 +19,6 @@ import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow import net.corda.flows.CashFlow
import net.corda.node.driver.DriverBasedTest import net.corda.node.driver.DriverBasedTest
import net.corda.node.driver.driver import net.corda.node.driver.driver
@ -94,7 +93,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test @Test
fun `cash issue works end to end`() { fun `cash issue works end to end`() {
rpc.startFlow(::CashFlow, CashCommand.IssueCash( rpc.startFlow(::CashFlow, CashFlow.Command.IssueCash(
amount = Amount(100, USD), amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })), issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity, recipient = aliceNode.legalIdentity,
@ -119,14 +118,14 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test @Test
fun `cash issue and move`() { fun `cash issue and move`() {
rpc.startFlow(::CashFlow, CashCommand.IssueCash( rpc.startFlow(::CashFlow, CashFlow.Command.IssueCash(
amount = Amount(100, USD), amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })), issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity, recipient = aliceNode.legalIdentity,
notary = notaryNode.notaryIdentity notary = notaryNode.notaryIdentity
)).returnValue.getOrThrow() )).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)), amount = Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
recipient = aliceNode.legalIdentity recipient = aliceNode.legalIdentity
)) ))

View File

@ -5,7 +5,7 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.flows.CashCommand import net.corda.flows.CashFlow
import java.util.* import java.util.*
/** /**
@ -64,7 +64,7 @@ class EventGenerator(
val issueCashGenerator = val issueCashGenerator =
amountGenerator.combine(partyGenerator, issueRefGenerator) { amount, to, issueRef -> amountGenerator.combine(partyGenerator, issueRefGenerator) { amount, to, issueRef ->
CashCommand.IssueCash( CashFlow.Command.IssueCash(
amount, amount,
issueRef, issueRef,
to, to,
@ -76,7 +76,7 @@ class EventGenerator(
amountIssuedGenerator.combine( amountIssuedGenerator.combine(
partyGenerator partyGenerator
) { amountIssued, recipient -> ) { amountIssued, recipient ->
CashCommand.PayCash( CashFlow.Command.PayCash(
amount = amountIssued, amount = amountIssued,
recipient = recipient recipient = recipient
) )
@ -84,7 +84,7 @@ class EventGenerator(
val exitCashGenerator = val exitCashGenerator =
amountIssuedGenerator.map { amountIssuedGenerator.map {
CashCommand.ExitCash( CashFlow.Command.ExitCash(
it.withoutIssuer(), it.withoutIssuer(),
it.token.issuer.reference it.token.issuer.reference
) )

View File

@ -9,7 +9,7 @@ import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashCommand import net.corda.core.toFuture
import net.corda.flows.CashFlow import net.corda.flows.CashFlow
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
@ -56,7 +56,7 @@ class IntegrationTestingTutorial {
val issueRef = OpaqueBytes.of(0) val issueRef = OpaqueBytes.of(0)
for (i in 1 .. 10) { for (i in 1 .. 10) {
thread { thread {
aliceProxy.startFlow(::CashFlow, CashCommand.IssueCash( aliceProxy.startFlow(::CashFlow, CashFlow.Command.IssueCash(
amount = i.DOLLARS, amount = i.DOLLARS,
issueRef = issueRef, issueRef = issueRef,
recipient = bob.nodeInfo.legalIdentity, recipient = bob.nodeInfo.legalIdentity,
@ -82,7 +82,7 @@ class IntegrationTestingTutorial {
// START 5 // START 5
for (i in 1 .. 10) { 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)), amount = i.DOLLARS.issuedBy(alice.nodeInfo.legalIdentity.ref(issueRef)),
recipient = alice.nodeInfo.legalIdentity recipient = alice.nodeInfo.legalIdentity
)) ))
@ -102,4 +102,4 @@ class IntegrationTestingTutorial {
} }
} }
} }
// END 5 // END 5

View File

@ -12,8 +12,9 @@ import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand import net.corda.flows.CashExitFlow
import net.corda.flows.CashFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
@ -41,7 +42,9 @@ fun main(args: Array<String>) {
val printOrVisualise = PrintOrVisualise.valueOf(args[0]) val printOrVisualise = PrintOrVisualise.valueOf(args[0])
val baseDirectory = Paths.get("build/rpc-api-tutorial") val baseDirectory = Paths.get("build/rpc-api-tutorial")
val user = User("user", "password", permissions = setOf(startFlowPermission<CashFlow>())) val user = User("user", "password", permissions = setOf(startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashExitFlow>()))
driver(driverDirectory = baseDirectory) { driver(driverDirectory = baseDirectory) {
startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))) startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
@ -114,14 +117,14 @@ fun generateTransactions(proxy: CordaRPCOps) {
val n = random.nextDouble() val n = random.nextDouble()
if (ownedQuantity > 10000 && n > 0.8) { if (ownedQuantity > 10000 && n > 0.8) {
val quantity = Math.abs(random.nextLong()) % 2000 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 ownedQuantity -= quantity
} else if (ownedQuantity > 1000 && n < 0.7) { } else if (ownedQuantity > 1000 && n < 0.7) {
val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000)) 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 { } else {
val quantity = Math.abs(random.nextLong() % 1000) 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 ownedQuantity += quantity
} }
} }

View File

@ -1,6 +1,5 @@
package net.corda.docs package net.corda.docs
import net.corda.core.crypto.Party
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo 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.toFuture
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.flows.CashCommand import net.corda.flows.CashIssueFlow
import net.corda.flows.CashFlow import net.corda.flows.CashPaymentFlow
import net.corda.core.node.ServiceEntry
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.node.utilities.databaseTransaction import net.corda.node.utilities.databaseTransaction
@ -51,19 +49,19 @@ class FxTransactionBuildTutorialTest {
@Test @Test
fun `Run ForeignExchangeFlow to completion`() { fun `Run ForeignExchangeFlow to completion`() {
// Use NodeA as issuer and create some dollars // Use NodeA as issuer and create some dollars
val flowHandle1 = nodeA.services.startFlow(CashFlow(CashCommand.IssueCash(DOLLARS(1000), val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(DOLLARS(1000),
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
nodeA.info.legalIdentity, nodeA.info.legalIdentity,
notaryNode.info.notaryIdentity))) notaryNode.info.notaryIdentity))
// Wait for the flow to stop and print // Wait for the flow to stop and print
flowHandle1.resultFuture.getOrThrow() flowHandle1.resultFuture.getOrThrow()
printBalances() printBalances()
// Using NodeB as Issuer create some pounds. // Using NodeB as Issuer create some pounds.
val flowHandle2 = nodeB.services.startFlow(CashFlow(CashCommand.IssueCash(POUNDS(1000), val flowHandle2 = nodeB.services.startFlow(CashIssueFlow(POUNDS(1000),
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
nodeB.info.legalIdentity, nodeB.info.legalIdentity,
notaryNode.info.notaryIdentity))) notaryNode.info.notaryIdentity))
// Wait for flow to come to an end and print // Wait for flow to come to an end and print
flowHandle2.resultFuture.getOrThrow() flowHandle2.resultFuture.getOrThrow()
printBalances() printBalances()
@ -107,4 +105,4 @@ class FxTransactionBuildTutorialTest {
println("BalanceB\n" + nodeB.services.vaultService.cashBalances) println("BalanceB\n" + nodeB.services.vaultService.cashBalances)
} }
} }
} }

View File

@ -259,8 +259,8 @@ Launch the Explorer application to visualize the issuance and transfer of cash f
Using the following login details: Using the following login details:
- For the Bank of Corda node: localhost / port 10004 / 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 user1 / 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. See https://docs.corda.net/node-explorer.html for further details on usage.

View File

@ -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<SignedTransaction>() {
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<Party>, 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)

View File

@ -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<Currency>, val issueRef: OpaqueBytes, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) {
constructor(amount: Amount<Currency>, 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<Cash.State>().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<Party> = inputStates
.filterIsInstance<Cash.State>()
.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
}
}

View File

@ -1,19 +1,13 @@
package net.corda.flows package net.corda.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash import net.corda.core.contracts.Amount
import net.corda.core.contracts.* import net.corda.core.contracts.Issued
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party 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.flows.FlowLogic
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import java.security.KeyPair
import java.util.* import java.util.*
/** /**
@ -21,8 +15,8 @@ import java.util.*
* *
* @param command Indicates what Cash transaction to create with what parameters. * @param command Indicates what Cash transaction to create with what parameters.
*/ */
class CashFlow(val command: CashCommand, override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() { class CashFlow(val command: CashFlow.Command, override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
constructor(command: CashCommand) : this(command, tracker()) constructor(command: CashFlow.Command) : this(command, tracker())
companion object { companion object {
object ISSUING : ProgressTracker.Step("Issuing cash") object ISSUING : ProgressTracker.Step("Issuing cash")
@ -36,133 +30,38 @@ class CashFlow(val command: CashCommand, override val progressTracker: ProgressT
@Throws(CashException::class) @Throws(CashException::class)
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
return when (command) { return when (command) {
is CashCommand.IssueCash -> issueCash(command) is CashFlow.Command.IssueCash -> subFlow(CashIssueFlow(command.amount, command.issueRef, command.recipient, command.notary))
is CashCommand.PayCash -> initiatePayment(command) is CashFlow.Command.PayCash -> subFlow(CashPaymentFlow(command.amount, command.recipient))
is CashCommand.ExitCash -> exitCash(command) is CashFlow.Command.ExitCash -> subFlow(CashExitFlow(command.amount, command.issueRef))
} }
} }
// TODO check with the recipient if they want to accept the cash. /**
@Suspendable * A command to initiate the Cash flow with.
private fun initiatePayment(req: CashCommand.PayCash): SignedTransaction { */
progressTracker.currentStep = PAYING sealed class Command {
val builder: TransactionBuilder = TransactionType.General.Builder(null) /**
// TODO: Have some way of restricting this to states the caller controls * A command to initiate the Cash flow with.
val (spendTX, keysForSigning) = try { */
serviceHub.vaultService.generateSpend( class IssueCash(val amount: Amount<Currency>,
builder, val issueRef: OpaqueBytes,
req.amount.withoutIssuer(), val recipient: Party,
req.recipient.owningKey, val notary: Party) : CashFlow.Command()
setOf(req.amount.token.issuer.party))
} catch (e: InsufficientBalanceException) {
throw CashException("Insufficent cash for spend", e)
}
keysForSigning.keys.forEach { /**
val key = serviceHub.keyManagementService.keys[it] ?: throw IllegalStateException("Could not find signing key for ${it.toStringShort()}") * Pay cash to someone else.
builder.signWith(KeyPair(it, key)) *
} * @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<Issued<Currency>>, val recipient: Party) : CashFlow.Command()
val tx = spendTX.toSignedTransaction(checkSufficientSignatures = false) /**
finaliseTx(setOf(req.recipient), tx, "Unable to notarise spend") * Exit cash from the ledger.
return tx *
} * @param amount the amount of currency to exit from the ledger.
* @param issueRef the reference previously specified on the issuance.
@Suspendable */
private fun exitCash(req: CashCommand.ExitCash): SignedTransaction { class ExitCash(val amount: Amount<Currency>, val issueRef: OpaqueBytes) : CashFlow.Command()
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<Cash.State>().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<Party> = inputStates
.filterIsInstance<Cash.State>()
.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<Party>, 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
} }
} }
/**
* 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<Currency>,
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<Issued<Currency>>, 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<Currency>, val issueRef: OpaqueBytes) : CashCommand()
}
class CashException(message: String, cause: Throwable) : FlowException(message, cause)

View File

@ -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<Currency>,
val issueRef: OpaqueBytes,
val recipient: Party,
val notary: Party,
progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) {
constructor(amount: Amount<Currency>,
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
}
}

View File

@ -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<Issued<Currency>>, val recipient: Party, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) {
constructor(amount: Amount<Issued<Currency>>, 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
}
}

View File

@ -79,7 +79,7 @@ object IssuerFlow {
// invoke Cash subflow to issue Asset // invoke Cash subflow to issue Asset
progressTracker.currentStep = ISSUING progressTracker.currentStep = ISSUING
val bankOfCordaParty = serviceHub.myInfo.legalIdentity 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) val issueTx = subFlow(issueCashFlow)
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger) // NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
// short-circuit when issuing to self // short-circuit when issuing to self
@ -87,7 +87,7 @@ object IssuerFlow {
return issueTx return issueTx
// now invoke Cash subflow to Move issued assetType to issue requester // now invoke Cash subflow to Move issued assetType to issue requester
progressTracker.currentStep = TRANSFERRING 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) val moveTx = subFlow(moveCashFlow)
// NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger) // NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger)
return moveTx return moveTx

View File

@ -11,8 +11,9 @@ import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashCommand import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.driver.DriverBasedTest import net.corda.node.driver.DriverBasedTest
import net.corda.node.driver.NodeHandle import net.corda.node.driver.NodeHandle
import net.corda.node.driver.driver import net.corda.node.driver.driver
@ -35,7 +36,10 @@ class DistributedServiceTests : DriverBasedTest() {
override fun setup() = driver { override fun setup() = driver {
// Start Alice and 3 notaries in a RAFT cluster // Start Alice and 3 notaries in a RAFT cluster
val clusterSize = 3 val clusterSize = 3
val testUser = User("test", "test", permissions = setOf(startFlowPermission<CashFlow>())) val testUser = User("test", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>())
)
val aliceFuture = startNode("Alice", rpcUsers = listOf(testUser)) val aliceFuture = startNode("Alice", rpcUsers = listOf(testUser))
val notariesFuture = startNotaryCluster( val notariesFuture = startNotaryCluster(
"Notary", "Notary",
@ -135,15 +139,15 @@ class DistributedServiceTests : DriverBasedTest() {
private fun issueCash(amount: Amount<Currency>) { private fun issueCash(amount: Amount<Currency>) {
val issueHandle = aliceProxy.startFlow( val issueHandle = aliceProxy.startFlow(
::CashFlow, ::CashIssueFlow,
CashCommand.IssueCash(amount, OpaqueBytes.of(0), alice.nodeInfo.legalIdentity, raftNotaryIdentity)) amount, OpaqueBytes.of(0), alice.nodeInfo.legalIdentity, raftNotaryIdentity)
issueHandle.returnValue.getOrThrow() issueHandle.returnValue.getOrThrow()
} }
private fun paySelf(amount: Amount<Currency>) { private fun paySelf(amount: Amount<Currency>) {
val payHandle = aliceProxy.startFlow( val payHandle = aliceProxy.startFlow(
::CashFlow, ::CashPaymentFlow,
CashCommand.PayCash(amount.issuedBy(alice.nodeInfo.legalIdentity.ref(0)), alice.nodeInfo.legalIdentity)) amount.issuedBy(alice.nodeInfo.legalIdentity.ref(0)), alice.nodeInfo.legalIdentity)
payHandle.returnValue.getOrThrow() payHandle.returnValue.getOrThrow()
} }
} }

View File

@ -6,6 +6,8 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.SettableFuture
import net.corda.core.* 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.Party
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.flows.FlowLogic 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.*
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.node.services.NetworkMapCache.MapChange 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.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand import net.corda.flows.*
import net.corda.flows.CashFlow
import net.corda.flows.FinalityFlow
import net.corda.flows.sendRequest
import net.corda.node.services.api.* import net.corda.node.services.api.*
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate 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.apache.activemq.artemis.utils.ReusableLatch
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.slf4j.Logger import org.slf4j.Logger
import java.io.File
import java.nio.file.FileAlreadyExistsException import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
@ -82,11 +81,12 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
val PUBLIC_IDENTITY_FILE_NAME = "identity-public" val PUBLIC_IDENTITY_FILE_NAME = "identity-public"
val defaultFlowWhiteList: Map<Class<out FlowLogic<*>>, Set<Class<*>>> = mapOf( val defaultFlowWhiteList: Map<Class<out FlowLogic<*>>, Set<Class<*>>> = mapOf(
CashFlow::class.java to setOf( CashFlow::class.java to setOf(CashFlow.Command.IssueCash::class.java,
CashCommand.IssueCash::class.java, CashFlow.Command.PayCash::class.java,
CashCommand.PayCash::class.java, CashFlow.Command.ExitCash::class.java),
CashCommand.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() FinalityFlow::class.java to emptySet()
) )
} }

View File

@ -9,8 +9,8 @@ import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand import net.corda.flows.CashIssueFlow
import net.corda.flows.CashFlow import net.corda.flows.CashPaymentFlow
import net.corda.node.internal.CordaRPCOpsImpl import net.corda.node.internal.CordaRPCOpsImpl
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.messaging.CURRENT_RPC_USER import net.corda.node.services.messaging.CURRENT_RPC_USER
@ -48,7 +48,10 @@ class CordaRPCOpsImplTest {
aliceNode = network.createNode(networkMapAddress = networkMap.info.address) aliceNode = network.createNode(networkMapAddress = networkMap.info.address)
notaryNode = network.createNode(advertisedServices = ServiceInfo(SimpleNotaryService.type), networkMapAddress = networkMap.info.address) notaryNode = network.createNode(advertisedServices = ServiceInfo(SimpleNotaryService.type), networkMapAddress = networkMap.info.address)
rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database) rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database)
CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(startFlowPermission<CashFlow>()))) CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
)))
databaseTransaction(aliceNode.database) { databaseTransaction(aliceNode.database) {
stateMachineUpdates = rpc.stateMachinesAndUpdates().second stateMachineUpdates = rpc.stateMachinesAndUpdates().second
@ -69,8 +72,7 @@ class CordaRPCOpsImplTest {
// Tell the monitoring service node to issue some cash // Tell the monitoring service node to issue some cash
val recipient = aliceNode.info.legalIdentity val recipient = aliceNode.info.legalIdentity
val outEvent = CashCommand.IssueCash(Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity) rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity)
rpc.startFlow(::CashFlow, outEvent)
network.runNetwork() network.runNetwork()
val expectedState = Cash.State(Amount(quantity, val expectedState = Cash.State(Amount(quantity,
@ -107,19 +109,19 @@ class CordaRPCOpsImplTest {
@Test @Test
fun `issue and move`() { fun `issue and move`() {
rpc.startFlow(::CashFlow, CashCommand.IssueCash( rpc.startFlow(::CashIssueFlow,
amount = Amount(100, USD), Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })), OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.info.legalIdentity, aliceNode.info.legalIdentity,
notary = notaryNode.info.notaryIdentity notaryNode.info.notaryIdentity
)) )
network.runNetwork() network.runNetwork()
rpc.startFlow(::CashFlow, CashCommand.PayCash( rpc.startFlow(::CashPaymentFlow,
amount = Amount(100, Issued(PartyAndReference(aliceNode.info.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)), Amount(100, Issued(PartyAndReference(aliceNode.info.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
recipient = aliceNode.info.legalIdentity aliceNode.info.legalIdentity
)) )
network.runNetwork() network.runNetwork()
@ -188,12 +190,12 @@ class CordaRPCOpsImplTest {
fun `cash command by user not permissioned for cash`() { fun `cash command by user not permissioned for cash`() {
CURRENT_RPC_USER.set(User("user", "pwd", permissions = emptySet())) CURRENT_RPC_USER.set(User("user", "pwd", permissions = emptySet()))
assertThatExceptionOfType(PermissionException::class.java).isThrownBy { assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
rpc.startFlow(::CashFlow, CashCommand.IssueCash( rpc.startFlow(::CashIssueFlow,
amount = Amount(100, USD), Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })), OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.info.legalIdentity, aliceNode.info.legalIdentity,
notary = notaryNode.info.notaryIdentity notaryNode.info.notaryIdentity
)) )
} }
} }
} }

View File

@ -24,8 +24,8 @@ import net.corda.core.serialization.deserialize
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.flows.CashCommand import net.corda.flows.CashIssueFlow
import net.corda.flows.CashFlow import net.corda.flows.CashPaymentFlow
import net.corda.flows.FinalityFlow import net.corda.flows.FinalityFlow
import net.corda.flows.NotaryFlow import net.corda.flows.NotaryFlow
import net.corda.node.services.persistence.checkpoints import net.corda.node.services.persistence.checkpoints
@ -320,16 +320,16 @@ class StateMachineManagerTests {
@Test @Test
fun `different notaries are picked when addressing shared notary identity`() { fun `different notaries are picked when addressing shared notary identity`() {
assertEquals(notary1.info.notaryIdentity, notary2.info.notaryIdentity) assertEquals(notary1.info.notaryIdentity, notary2.info.notaryIdentity)
node1.services.startFlow(CashFlow(CashCommand.IssueCash( node1.services.startFlow(CashIssueFlow(
DOLLARS(2000), DOLLARS(2000),
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
node1.info.legalIdentity, node1.info.legalIdentity,
notary1.info.notaryIdentity))) notary1.info.notaryIdentity))
// We pay a couple of times, the notary picking should go round robin // We pay a couple of times, the notary picking should go round robin
for (i in 1 .. 3) { for (i in 1 .. 3) {
node1.services.startFlow(CashFlow(CashCommand.PayCash( node1.services.startFlow(CashPaymentFlow(
DOLLARS(500).issuedBy(node1.info.legalIdentity.ref(0x01)), DOLLARS(500).issuedBy(node1.info.legalIdentity.ref(0x01)),
node2.info.legalIdentity))) node2.info.legalIdentity))
net.runNetwork() net.runNetwork()
} }
val endpoint = net.messagingNetwork.endpoint(notary1.net.myAddress as InMemoryMessagingNetwork.PeerHandle)!! val endpoint = net.messagingNetwork.endpoint(notary1.net.myAddress as InMemoryMessagingNetwork.PeerHandle)!!

View File

@ -41,9 +41,9 @@ Testing of the Bank of Corda application is demonstrated at two levels:
Security Security
The RPC API requires a client to pass in user credentials: 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: which are validated on the Bank of Corda node against those configured at node startup:
User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>())) User("bankUser", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
startNode("BankOfCorda", rpcUsers = listOf(user)) startNode("BankOfCorda", rpcUsers = listOf(user))
Notary Notary

View File

@ -77,9 +77,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
webPort 10005 webPort 10005
cordapps = [] cordapps = []
rpcUsers = [ rpcUsers = [
['user' : "user1", ['user' : "bankUser",
'password' : "test", 'password' : "test",
'permissions' : ["StartFlow.net.corda.flows.CashFlow", 'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow",
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]] "StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]]
] ]
} }
@ -91,9 +91,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
webPort 10007 webPort 10007
cordapps = [] cordapps = []
rpcUsers = [ rpcUsers = [
['user' : "user1", ['user' : "bigCorpUser",
'password' : "test", 'password' : "test",
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]] 'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow"]]
] ]
} }
} }

View File

@ -8,7 +8,7 @@ import net.corda.flows.IssuerFlow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType import net.corda.core.node.services.ServiceType
import net.corda.core.transactions.SignedTransaction 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.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
@ -51,8 +51,8 @@ private class BankOfCordaDriver {
val role = options.valueOf(roleArg)!! val role = options.valueOf(roleArg)!!
if (role == Role.ISSUER) { if (role == Role.ISSUER) {
driver(dsl = { driver(dsl = {
val bankUser = User(BANK_USERNAME, "test", permissions = setOf(startFlowPermission<CashFlow>(), startFlowPermission<IssuerFlow.IssuanceRequester>())) val bankUser = User(BANK_USERNAME, "test", permissions = setOf(startFlowPermission<CashPaymentFlow>(), startFlowPermission<IssuerFlow.IssuanceRequester>()))
val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf(startFlowPermission<CashFlow>())) val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf(startFlowPermission<CashPaymentFlow>()))
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type))) startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
val bankOfCorda = startNode("BankOfCorda", rpcUsers = listOf(bankUser), advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.USD")))) val bankOfCorda = startNode("BankOfCorda", rpcUsers = listOf(bankUser), advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.USD"))))
startNode("BigCorporation", rpcUsers = listOf(bigCorpUser)) startNode("BigCorporation", rpcUsers = listOf(bigCorpUser))

View File

@ -32,7 +32,7 @@ class BankOfCordaClientApi(val hostAndPort: HostAndPort) {
fun requestRPCIssue(params: IssueRequestParams): SignedTransaction { fun requestRPCIssue(params: IssueRequestParams): SignedTransaction {
val client = CordaRPCClient(hostAndPort, configureTestSSL()) val client = CordaRPCClient(hostAndPort, configureTestSSL())
// TODO: privileged security controls required // TODO: privileged security controls required
client.start("user1", "test") client.start("bankUser", "test")
val proxy = client.proxy() val proxy = client.proxy()
// Resolve parties via RPC // Resolve parties via RPC

View File

@ -28,7 +28,6 @@ import net.corda.explorer.model.ReportingCurrencyModel
import net.corda.explorer.views.bigDecimalFormatter import net.corda.explorer.views.bigDecimalFormatter
import net.corda.explorer.views.byteFormatter import net.corda.explorer.views.byteFormatter
import net.corda.explorer.views.stringConverter import net.corda.explorer.views.stringConverter
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow import net.corda.flows.CashFlow
import net.corda.flows.IssuerFlow.IssuanceRequester import net.corda.flows.IssuerFlow.IssuanceRequester
import org.controlsfx.dialog.ExceptionDialog import org.controlsfx.dialog.ExceptionDialog
@ -88,7 +87,7 @@ class NewTransaction : Fragment() {
} }
dialog.show() dialog.show()
runAsync { runAsync {
val handle = if (it is CashCommand.IssueCash) { val handle = if (it is CashFlow.Command.IssueCash) {
myIdentity.value?.let { myIdentity -> myIdentity.value?.let { myIdentity ->
rpcProxy.value!!.startFlow(::IssuanceRequester, rpcProxy.value!!.startFlow(::IssuanceRequester,
it.amount, it.amount,
@ -111,9 +110,9 @@ class NewTransaction : Fragment() {
Alert.AlertType.ERROR to response.message Alert.AlertType.ERROR to response.message
} else { } else {
val type = when (command) { val type = when (command) {
is CashCommand.IssueCash -> "Cash Issued" is CashFlow.Command.IssueCash -> "Cash Issued"
is CashCommand.ExitCash -> "Cash Exited" is CashFlow.Command.ExitCash -> "Cash Exited"
is CashCommand.PayCash -> "Cash Paid" is CashFlow.Command.PayCash -> "Cash Paid"
} }
Alert.AlertType.INFORMATION to "$type \nTransaction ID : ${(response as SignedTransaction).id}" Alert.AlertType.INFORMATION to "$type \nTransaction ID : ${(response as SignedTransaction).id}"
} }
@ -128,7 +127,7 @@ class NewTransaction : Fragment() {
} }
} }
private fun dialog(window: Window) = Dialog<CashCommand>().apply { private fun dialog(window: Window) = Dialog<CashFlow.Command>().apply {
dialogPane = root dialogPane = root
initOwner(window) initOwner(window)
setResultConverter { setResultConverter {
@ -137,10 +136,10 @@ class NewTransaction : Fragment() {
when (it) { when (it) {
executeButton -> when (transactionTypeCB.value) { executeButton -> when (transactionTypeCB.value) {
CashTransaction.Issue -> { 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.Pay -> CashFlow.Command.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.Exit -> CashFlow.Command.ExitCash(Amount(amount.value, currencyChoiceBox.value), issueRef)
else -> null else -> null
} }
else -> null else -> null

View File

@ -12,7 +12,8 @@ import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.serialization.OpaqueBytes 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.flows.CashFlow
import net.corda.loadtest.LoadTest import net.corda.loadtest.LoadTest
import net.corda.loadtest.NodeHandle import net.corda.loadtest.NodeHandle
@ -28,18 +29,18 @@ private val log = LoggerFactory.getLogger("CrossCash")
*/ */
data class CrossCashCommand( data class CrossCashCommand(
val command: CashCommand, val command: CashFlow.Command,
val node: NodeHandle val node: NodeHandle
) { ) {
override fun toString(): String { override fun toString(): String {
return when (command) { return when (command) {
is CashCommand.IssueCash -> { is CashFlow.Command.IssueCash -> {
"ISSUE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}" "ISSUE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}"
} }
is CashCommand.PayCash -> { is CashFlow.Command.PayCash -> {
"MOVE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}" "MOVE ${node.info.legalIdentity} -> ${command.recipient} : ${command.amount}"
} }
is CashCommand.ExitCash -> { is CashFlow.Command.ExitCash -> {
"EXIT ${node.info.legalIdentity} : ${command.amount}" "EXIT ${node.info.legalIdentity} : ${command.amount}"
} }
} }
@ -145,7 +146,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
interpret = { state, command -> interpret = { state, command ->
when (command.command) { when (command.command) {
is CashCommand.IssueCash -> { is CashFlow.Command.IssueCash -> {
val newDiffQueues = state.copyQueues() val newDiffQueues = state.copyQueues()
val originators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() }) val originators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() })
val issuer = command.node.info.legalIdentity val issuer = command.node.info.legalIdentity
@ -155,7 +156,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
queue.add(Pair(issuer, quantity)) queue.add(Pair(issuer, quantity))
CrossCashState(state.nodeVaults, newDiffQueues) CrossCashState(state.nodeVaults, newDiffQueues)
} }
is CashCommand.PayCash -> { is CashFlow.Command.PayCash -> {
val newNodeVaults = state.copyVaults() val newNodeVaults = state.copyVaults()
val newDiffQueues = state.copyQueues() val newDiffQueues = state.copyQueues()
val recipientOriginators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() }) val recipientOriginators = newDiffQueues.getOrPut(command.command.recipient, { HashMap() })
@ -182,7 +183,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
recipientQueue.add(Pair(issuer, quantity)) recipientQueue.add(Pair(issuer, quantity))
CrossCashState(newNodeVaults, newDiffQueues) CrossCashState(newNodeVaults, newDiffQueues)
} }
is CashCommand.ExitCash -> { is CashFlow.Command.ExitCash -> {
val newNodeVaults = state.copyVaults() val newNodeVaults = state.copyVaults()
val issuer = command.node.info.legalIdentity val issuer = command.node.info.legalIdentity
val quantity = command.command.amount.quantity val quantity = command.command.amount.quantity

View File

@ -8,7 +8,7 @@ import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.AnonymousParty import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashCommand import net.corda.flows.CashFlow
import java.util.* import java.util.*
fun generateIssue( fun generateIssue(
@ -16,12 +16,12 @@ fun generateIssue(
currency: Currency, currency: Currency,
notary: Party, notary: Party,
possibleRecipients: List<Party> possibleRecipients: List<Party>
): Generator<CashCommand.IssueCash> { ): Generator<CashFlow.Command.IssueCash> {
return generateAmount(0, max, Generator.pure(currency)).combine( return generateAmount(0, max, Generator.pure(currency)).combine(
Generator.pure(OpaqueBytes.of(0)), Generator.pure(OpaqueBytes.of(0)),
Generator.pickOne(possibleRecipients) Generator.pickOne(possibleRecipients)
) { amount, ref, recipient -> ) { amount, ref, recipient ->
CashCommand.IssueCash(amount, ref, recipient, notary) CashFlow.Command.IssueCash(amount, ref, recipient, notary)
} }
} }
@ -30,19 +30,19 @@ fun generateMove(
currency: Currency, currency: Currency,
issuer: AnonymousParty, issuer: AnonymousParty,
possibleRecipients: List<Party> possibleRecipients: List<Party>
): Generator<CashCommand.PayCash> { ): Generator<CashFlow.Command.PayCash> {
return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine( return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine(
Generator.pickOne(possibleRecipients) Generator.pickOne(possibleRecipients)
) { amount, recipient -> ) { amount, recipient ->
CashCommand.PayCash(amount, recipient) CashFlow.Command.PayCash(amount, recipient)
} }
} }
fun generateExit( fun generateExit(
max: Long, max: Long,
currency: Currency currency: Currency
): Generator<CashCommand.ExitCash> { ): Generator<CashFlow.Command.ExitCash> {
return generateAmount(1, max, Generator.pure(currency)).map { amount -> return generateAmount(1, max, Generator.pure(currency)).map { amount ->
CashCommand.ExitCash(amount, OpaqueBytes.of(0)) CashFlow.Command.ExitCash(amount, OpaqueBytes.of(0))
} }
} }

View File

@ -11,7 +11,8 @@ import net.corda.core.crypto.Party
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow 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.flows.CashFlow
import net.corda.loadtest.LoadTest import net.corda.loadtest.LoadTest
import net.corda.loadtest.NodeHandle import net.corda.loadtest.NodeHandle
@ -22,7 +23,7 @@ private val log = LoggerFactory.getLogger("SelfIssue")
// DOCS START 1 // DOCS START 1
data class SelfIssueCommand( data class SelfIssueCommand(
val command: CashCommand.IssueCash, val command: CashFlow.Command.IssueCash,
val node: NodeHandle val node: NodeHandle
) )