From f512bd2e719f1e3635dda7c24d6b99a91c0d498e Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 17 Jan 2017 11:41:20 +0000 Subject: [PATCH] Trader demo now uses RPC to node rather than via a HTTP server. --- .../net/corda/traderdemo/TraderDemoTest.kt | 14 +++- .../kotlin/net/corda/traderdemo/TraderDemo.kt | 27 ++++++- .../corda/traderdemo/TraderDemoClientApi.kt | 58 ++++++++++++-- .../net/corda/traderdemo/api/TraderDemoApi.kt | 77 ------------------- .../traderdemo/plugin/TraderDemoPlugin.kt | 3 - 5 files changed, 84 insertions(+), 95 deletions(-) delete mode 100644 samples/trader-demo/src/main/kotlin/net/corda/traderdemo/api/TraderDemoApi.kt diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index a3eb316d2e..b956df1bac 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -13,16 +13,22 @@ import org.junit.Test class TraderDemoTest { @Test fun `runs trader demo`() { driver(isDebug = true) { + val permissions = setOf( + startFlowPermission(), + startFlowPermission()) + val demoUser = listOf(User("demo", "demo", permissions)) val user = User("user1", "test", permissions = setOf(startFlowPermission())) val (nodeA, nodeB) = Futures.allAsList( - startNode("Bank A"), - startNode("Bank B"), + startNode("Bank A", rpcUsers = demoUser), + startNode("Bank B", rpcUsers = demoUser), startNode("BankOfCorda", rpcUsers = listOf(user)), startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type))) ).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(nodeB.configuration.webAddress).runSeller(counterparty = nodeA.nodeInfo.legalIdentity.name)) + assert(TraderDemoClientApi(nodeARpc).runBuyer()) + assert(TraderDemoClientApi(nodeBRpc).runSeller(counterparty = nodeA.nodeInfo.legalIdentity.name)) } } } diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt index 4e5024579b..dc0652ee22 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt @@ -3,8 +3,13 @@ package net.corda.traderdemo import com.google.common.net.HostAndPort import joptsimple.OptionParser import net.corda.core.contracts.DOLLARS +import net.corda.core.div 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 java.nio.file.Path +import java.nio.file.Paths import kotlin.system.exitProcess /** @@ -26,6 +31,7 @@ private class TraderDemo { fun main(args: Array) { val parser = OptionParser() + val certsPath = parser.accepts("certificates").withRequiredArg() val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required() val options = try { @@ -40,9 +46,15 @@ private class TraderDemo { // will contact the buyer and actually make something happen. val role = options.valueOf(roleArg)!! if (role == Role.BUYER) { - TraderDemoClientApi(HostAndPort.fromString("localhost:10005")).runBuyer() + val host = HostAndPort.fromString("localhost:10004") + CordaRPCClient(host, sslConfigFor("nodea", options.valueOf(certsPath))).use("demo", "demo") { + TraderDemoClientApi(this).runBuyer() + } } else { - TraderDemoClientApi(HostAndPort.fromString("localhost:10007")).runSeller(1000.DOLLARS, "Bank A") + val host = HostAndPort.fromString("localhost:10006") + CordaRPCClient(host, sslConfigFor("nodeb", options.valueOf(certsPath))).use("demo", "demo") { + TraderDemoClientApi(this).runSeller(1000.DOLLARS, "Bank A") + } } } @@ -54,6 +66,13 @@ private class TraderDemo { """.trimIndent()) 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" + } + } } - - diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt index 971cb2fb8a..bc63f62c23 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt @@ -1,26 +1,70 @@ package net.corda.traderdemo import com.google.common.net.HostAndPort +import net.corda.contracts.testing.calculateRandomlySizedAmounts import net.corda.core.contracts.Amount 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.traderdemo.flow.SellerFlow import java.util.* +import kotlin.test.assertEquals /** * Interface for communicating with nodes running the trader demo. */ -class TraderDemoClientApi(hostAndPort: HostAndPort) { - private val api = HttpApi.fromHostAndPort(hostAndPort, apiRoot) +class TraderDemoClientApi(val rpc: CordaRPCOps) { + private companion object { + val logger = loggerFor() + } fun runBuyer(amount: Amount = 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 = 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 { - private val apiRoot = "api/traderdemo" + // The line below blocks and waits for the future to resolve. + 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 + } } } diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/api/TraderDemoApi.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/api/TraderDemoApi.kt deleted file mode 100644 index 8d15efb7e2..0000000000 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/api/TraderDemoApi.kt +++ /dev/null @@ -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() - } - - /** - * 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() - } - } -} diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/plugin/TraderDemoPlugin.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/plugin/TraderDemoPlugin.kt index 499dd5da16..093740050e 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/plugin/TraderDemoPlugin.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/plugin/TraderDemoPlugin.kt @@ -5,14 +5,11 @@ import net.corda.core.crypto.Party import net.corda.core.node.CordaPluginRegistry import net.corda.core.serialization.OpaqueBytes import net.corda.flows.IssuerFlow -import net.corda.traderdemo.api.TraderDemoApi import net.corda.traderdemo.flow.BuyerFlow import net.corda.traderdemo.flow.SellerFlow import java.util.function.Function 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 override val requiredFlows: Map> = mapOf( SellerFlow::class.java.name to setOf(Party::class.java.name, Amount::class.java.name)