mirror of
https://github.com/corda/corda.git
synced 2025-01-12 07:52:38 +00:00
Merge pull request #161 from corda/clint-traderdemorpc
Trader demo now uses RPC to communicate with nodes
This commit is contained in:
commit
5d40a03e60
@ -3,7 +3,7 @@
|
|||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role BUYER" />
|
<option name="PROGRAM_PARAMETERS" value="--role BUYER --certificates="build/trader-demo-nodes/Bank A/certificates"" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
|
||||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role SELLER" />
|
<option name="PROGRAM_PARAMETERS" value="--role SELLER --certificates="build/trader-demo-nodes/Bank B/certificates"" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
@ -67,6 +67,11 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
||||||
|
ext.rpcUsers = [['user': "demo", 'password': "demo", 'permissions': [
|
||||||
|
'StartFlow.net.corda.flows.IssuerFlow$IssuanceRequester',
|
||||||
|
"StartFlow.net.corda.traderdemo.flow.SellerFlow"
|
||||||
|
]]]
|
||||||
|
|
||||||
directory "./build/nodes"
|
directory "./build/nodes"
|
||||||
// This name "Notary" is hard-coded into TraderDemoClientApi so if you change it here, change it there too.
|
// This name "Notary" is hard-coded into TraderDemoClientApi so if you change it here, change it there too.
|
||||||
// In this demo the node that runs a standalone notary also acts as the network map server.
|
// In this demo the node that runs a standalone notary also acts as the network map server.
|
||||||
@ -86,6 +91,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
|||||||
artemisPort 10004
|
artemisPort 10004
|
||||||
webPort 10005
|
webPort 10005
|
||||||
cordapps = []
|
cordapps = []
|
||||||
|
rpcUsers = ext.rpcUsers
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "Bank B"
|
name "Bank B"
|
||||||
@ -94,6 +100,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
|||||||
artemisPort 10006
|
artemisPort 10006
|
||||||
webPort 10007
|
webPort 10007
|
||||||
cordapps = []
|
cordapps = []
|
||||||
|
rpcUsers = ext.rpcUsers
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "BankOfCorda"
|
name "BankOfCorda"
|
||||||
|
@ -13,16 +13,22 @@ import org.junit.Test
|
|||||||
class TraderDemoTest {
|
class TraderDemoTest {
|
||||||
@Test fun `runs trader demo`() {
|
@Test fun `runs trader demo`() {
|
||||||
driver(isDebug = true) {
|
driver(isDebug = true) {
|
||||||
|
val permissions = setOf(
|
||||||
|
startFlowPermission<IssuerFlow.IssuanceRequester>(),
|
||||||
|
startFlowPermission<net.corda.traderdemo.flow.SellerFlow>())
|
||||||
|
val demoUser = listOf(User("demo", "demo", permissions))
|
||||||
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
|
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
|
||||||
val (nodeA, nodeB) = Futures.allAsList(
|
val (nodeA, nodeB) = Futures.allAsList(
|
||||||
startNode("Bank A"),
|
startNode("Bank A", rpcUsers = demoUser),
|
||||||
startNode("Bank B"),
|
startNode("Bank B", rpcUsers = demoUser),
|
||||||
startNode("BankOfCorda", rpcUsers = listOf(user)),
|
startNode("BankOfCorda", rpcUsers = listOf(user)),
|
||||||
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
|
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
).getOrThrow()
|
).getOrThrow()
|
||||||
|
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB)
|
||||||
|
.map { it.rpcClientToNode().start(demoUser[0].username, demoUser[0].password).proxy() }
|
||||||
|
|
||||||
assert(TraderDemoClientApi(nodeA.configuration.webAddress).runBuyer())
|
assert(TraderDemoClientApi(nodeARpc).runBuyer())
|
||||||
assert(TraderDemoClientApi(nodeB.configuration.webAddress).runSeller(counterparty = nodeA.nodeInfo.legalIdentity.name))
|
assert(TraderDemoClientApi(nodeBRpc).runSeller(counterparty = nodeA.nodeInfo.legalIdentity.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,24 @@ import net.corda.node.services.User
|
|||||||
import net.corda.node.services.startFlowPermission
|
import net.corda.node.services.startFlowPermission
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
import net.corda.testing.BOC
|
import net.corda.testing.BOC
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import net.corda.core.div
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file is exclusively for being able to run your nodes through an IDE (as opposed to running deployNodes)
|
* This file is exclusively for being able to run your nodes through an IDE (as opposed to running deployNodes)
|
||||||
* Do not use in a production environment.
|
* Do not use in a production environment.
|
||||||
*/
|
*/
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
driver(dsl = {
|
val permissions = setOf(
|
||||||
|
startFlowPermission<IssuerFlow.IssuanceRequester>(),
|
||||||
|
startFlowPermission<net.corda.traderdemo.flow.SellerFlow>())
|
||||||
|
val demoUser = listOf(User("demo", "demo", permissions))
|
||||||
|
driver(driverDirectory = Paths.get("build") / "trader-demo-nodes", isDebug = true) {
|
||||||
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
|
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
|
||||||
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
|
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
startNode("Bank A")
|
startNode("Bank A", rpcUsers = demoUser)
|
||||||
startNode("Bank B")
|
startNode("Bank B", rpcUsers = demoUser)
|
||||||
startNode(BOC.name, rpcUsers = listOf(user))
|
startNode(BOC.name, rpcUsers = listOf(user))
|
||||||
waitForAllNodesToFinish()
|
waitForAllNodesToFinish()
|
||||||
}, isDebug = true)
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,13 @@ package net.corda.traderdemo
|
|||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import joptsimple.OptionParser
|
import joptsimple.OptionParser
|
||||||
import net.corda.core.contracts.DOLLARS
|
import net.corda.core.contracts.DOLLARS
|
||||||
|
import net.corda.core.div
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import net.corda.node.services.config.NodeSSLConfiguration
|
||||||
|
import net.corda.node.services.messaging.CordaRPCClient
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,6 +31,7 @@ private class TraderDemo {
|
|||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val parser = OptionParser()
|
val parser = OptionParser()
|
||||||
|
val certsPath = parser.accepts("certificates").withRequiredArg()
|
||||||
|
|
||||||
val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required()
|
val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required()
|
||||||
val options = try {
|
val options = try {
|
||||||
@ -40,9 +46,15 @@ private class TraderDemo {
|
|||||||
// will contact the buyer and actually make something happen.
|
// will contact the buyer and actually make something happen.
|
||||||
val role = options.valueOf(roleArg)!!
|
val role = options.valueOf(roleArg)!!
|
||||||
if (role == Role.BUYER) {
|
if (role == Role.BUYER) {
|
||||||
TraderDemoClientApi(HostAndPort.fromString("localhost:10005")).runBuyer()
|
val host = HostAndPort.fromString("localhost:10004")
|
||||||
|
CordaRPCClient(host, sslConfigFor("BankA", options.valueOf(certsPath))).use("demo", "demo") {
|
||||||
|
TraderDemoClientApi(this).runBuyer()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
TraderDemoClientApi(HostAndPort.fromString("localhost:10007")).runSeller(1000.DOLLARS, "Bank A")
|
val host = HostAndPort.fromString("localhost:10006")
|
||||||
|
CordaRPCClient(host, sslConfigFor("BankB", options.valueOf(certsPath))).use("demo", "demo") {
|
||||||
|
TraderDemoClientApi(this).runSeller(1000.DOLLARS, "Bank A")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +66,13 @@ private class TraderDemo {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
parser.printHelpOn(System.out)
|
parser.printHelpOn(System.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Take this out once we have a dedicated RPC port and allow SSL on it to be optional.
|
||||||
|
private fun sslConfigFor(nodename: String, certsPath: String?): NodeSSLConfiguration {
|
||||||
|
return object : NodeSSLConfiguration {
|
||||||
|
override val keyStorePassword: String = "cordacadevpass"
|
||||||
|
override val trustStorePassword: String = "trustpass"
|
||||||
|
override val certificatesDirectory: Path = if (certsPath != null) Paths.get(certsPath) else Paths.get("build") / "nodes" / nodename / "certificates"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,26 +1,70 @@
|
|||||||
package net.corda.traderdemo
|
package net.corda.traderdemo
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
|
import net.corda.contracts.testing.calculateRandomlySizedAmounts
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.DOLLARS
|
import net.corda.core.contracts.DOLLARS
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
import net.corda.core.messaging.startFlow
|
||||||
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.utilities.Emoji
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import net.corda.flows.IssuerFlow.IssuanceRequester
|
||||||
|
import net.corda.node.services.messaging.CordaRPCClient
|
||||||
|
import net.corda.testing.BOC
|
||||||
import net.corda.testing.http.HttpApi
|
import net.corda.testing.http.HttpApi
|
||||||
|
import net.corda.traderdemo.flow.SellerFlow
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for communicating with nodes running the trader demo.
|
* Interface for communicating with nodes running the trader demo.
|
||||||
*/
|
*/
|
||||||
class TraderDemoClientApi(hostAndPort: HostAndPort) {
|
class TraderDemoClientApi(val rpc: CordaRPCOps) {
|
||||||
private val api = HttpApi.fromHostAndPort(hostAndPort, apiRoot)
|
private companion object {
|
||||||
|
val logger = loggerFor<TraderDemoClientApi>()
|
||||||
|
}
|
||||||
|
|
||||||
fun runBuyer(amount: Amount<Currency> = 30000.0.DOLLARS, notary: String = "Notary"): Boolean {
|
fun runBuyer(amount: Amount<Currency> = 30000.0.DOLLARS, notary: String = "Notary"): Boolean {
|
||||||
return api.putJson("create-test-cash", mapOf("amount" to amount.quantity, "notary" to notary))
|
val bankOfCordaParty = rpc.partyFromName(BOC.name)
|
||||||
|
?: throw Exception("Unable to locate ${BOC.name} in Network Map Service")
|
||||||
|
val me = rpc.nodeIdentity()
|
||||||
|
// TODO: revert back to multiple issue request amounts (3,10) when soft locking implemented
|
||||||
|
val amounts = calculateRandomlySizedAmounts(amount, 1, 1, Random())
|
||||||
|
val handles = amounts.map {
|
||||||
|
rpc.startFlow(::IssuanceRequester, amount, me.legalIdentity, OpaqueBytes.of(1), bankOfCordaParty)
|
||||||
|
}
|
||||||
|
|
||||||
|
handles.forEach {
|
||||||
|
require(it.returnValue.toBlocking().first() is SignedTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runSeller(amount: Amount<Currency> = 1000.0.DOLLARS, counterparty: String): Boolean {
|
fun runSeller(amount: Amount<Currency> = 1000.0.DOLLARS, counterparty: String): Boolean {
|
||||||
return api.postJson("$counterparty/sell-cash", mapOf("amount" to amount.quantity))
|
val otherParty = rpc.partyFromName(counterparty)
|
||||||
}
|
if (otherParty != null) {
|
||||||
|
// The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash.
|
||||||
|
//
|
||||||
|
// The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an
|
||||||
|
// attachment. Make sure we have the transaction prospectus attachment loaded into our store.
|
||||||
|
//
|
||||||
|
// This can also be done via an HTTP upload, but here we short-circuit and do it from code.
|
||||||
|
if (!rpc.attachmentExists(SellerFlow.PROSPECTUS_HASH)) {
|
||||||
|
javaClass.classLoader.getResourceAsStream("bank-of-london-cp.jar").use {
|
||||||
|
val id = rpc.uploadAttachment(it)
|
||||||
|
assertEquals(SellerFlow.PROSPECTUS_HASH, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
// The line below blocks and waits for the future to resolve.
|
||||||
private val apiRoot = "api/traderdemo"
|
val stx = rpc.startFlow(::SellerFlow, otherParty, amount).returnValue.toBlocking().first()
|
||||||
|
logger.info("Sale completed - we have a happy customer!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(stx.tx)}")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package net.corda.traderdemo.api
|
|
||||||
|
|
||||||
import net.corda.contracts.testing.calculateRandomlySizedAmounts
|
|
||||||
import net.corda.core.contracts.DOLLARS
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
|
||||||
import net.corda.core.messaging.startFlow
|
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.core.utilities.Emoji
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.flows.IssuerFlow.IssuanceRequester
|
|
||||||
import net.corda.testing.BOC
|
|
||||||
import net.corda.traderdemo.flow.SellerFlow
|
|
||||||
import java.util.*
|
|
||||||
import javax.ws.rs.*
|
|
||||||
import javax.ws.rs.core.MediaType
|
|
||||||
import javax.ws.rs.core.Response
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
// API is accessible from /api/traderdemo. All paths specified below are relative to it.
|
|
||||||
@Path("traderdemo")
|
|
||||||
class TraderDemoApi(val rpc: CordaRPCOps) {
|
|
||||||
data class TestCashParams(val amount: Int, val notary: String)
|
|
||||||
data class SellParams(val amount: Int)
|
|
||||||
private companion object {
|
|
||||||
val logger = loggerFor<TraderDemoApi>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses a central bank node (Bank of Corda) to request issuance of some cash.
|
|
||||||
*/
|
|
||||||
@PUT
|
|
||||||
@Path("create-test-cash")
|
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
|
||||||
fun createTestCash(params: TestCashParams): Response {
|
|
||||||
val bankOfCordaParty = rpc.partyFromName(BOC.name)
|
|
||||||
?: throw Exception("Unable to locate ${BOC.name} in Network Map Service")
|
|
||||||
val me = rpc.nodeIdentity()
|
|
||||||
// TODO: revert back to multiple issue request amounts (3,10) when soft locking implemented
|
|
||||||
val amounts = calculateRandomlySizedAmounts(params.amount.DOLLARS, 1, 1, Random())
|
|
||||||
val handles = amounts.map {
|
|
||||||
rpc.startFlow(::IssuanceRequester, params.amount.DOLLARS, me.legalIdentity, OpaqueBytes.of(1), bankOfCordaParty)
|
|
||||||
}
|
|
||||||
handles.forEach {
|
|
||||||
require(it.returnValue.toBlocking().first() is SignedTransaction)
|
|
||||||
}
|
|
||||||
return Response.status(Response.Status.CREATED).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("{party}/sell-cash")
|
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
|
||||||
fun sellCash(params: SellParams, @PathParam("party") partyName: String): Response {
|
|
||||||
val otherParty = rpc.partyFromName(partyName)
|
|
||||||
if (otherParty != null) {
|
|
||||||
// The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash.
|
|
||||||
//
|
|
||||||
// The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an
|
|
||||||
// attachment. Make sure we have the transaction prospectus attachment loaded into our store.
|
|
||||||
//
|
|
||||||
// This can also be done via an HTTP upload, but here we short-circuit and do it from code.
|
|
||||||
if (!rpc.attachmentExists(SellerFlow.PROSPECTUS_HASH)) {
|
|
||||||
javaClass.classLoader.getResourceAsStream("bank-of-london-cp.jar").use {
|
|
||||||
val id = rpc.uploadAttachment(it)
|
|
||||||
assertEquals(SellerFlow.PROSPECTUS_HASH, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The line below blocks and waits for the future to resolve.
|
|
||||||
val stx = rpc.startFlow(::SellerFlow, otherParty, params.amount.DOLLARS).returnValue.toBlocking().first()
|
|
||||||
logger.info("Sale completed - we have a happy customer!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(stx.tx)}")
|
|
||||||
return Response.status(Response.Status.OK).build()
|
|
||||||
} else {
|
|
||||||
return Response.status(Response.Status.BAD_REQUEST).build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,14 +5,11 @@ import net.corda.core.crypto.Party
|
|||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.flows.IssuerFlow
|
import net.corda.flows.IssuerFlow
|
||||||
import net.corda.traderdemo.api.TraderDemoApi
|
|
||||||
import net.corda.traderdemo.flow.BuyerFlow
|
import net.corda.traderdemo.flow.BuyerFlow
|
||||||
import net.corda.traderdemo.flow.SellerFlow
|
import net.corda.traderdemo.flow.SellerFlow
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
class TraderDemoPlugin : CordaPluginRegistry() {
|
class TraderDemoPlugin : CordaPluginRegistry() {
|
||||||
// A list of classes that expose web APIs.
|
|
||||||
override val webApis = listOf(Function(::TraderDemoApi))
|
|
||||||
// A list of Flows that are required for this cordapp
|
// A list of Flows that are required for this cordapp
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
||||||
SellerFlow::class.java.name to setOf(Party::class.java.name, Amount::class.java.name)
|
SellerFlow::class.java.name to setOf(Party::class.java.name, Amount::class.java.name)
|
||||||
|
Loading…
Reference in New Issue
Block a user