mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
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:
parent
47d260625a
commit
9055c9d9b0
@ -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()
|
||||||
|
@ -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
|
||||||
))
|
))
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
32
finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt
Normal file
32
finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt
Normal 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)
|
67
finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt
Normal file
67
finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
|
||||||
|
46
finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt
Normal file
46
finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
49
finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt
Normal file
49
finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)!!
|
||||||
|
@ -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
|
||||||
|
@ -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"]]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user