mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
Trader demo now uses RPC to node rather than via a HTTP server.
This commit is contained in:
parent
429fbb3b97
commit
f512bd2e71
@ -13,16 +13,22 @@ import org.junit.Test
|
||||
class TraderDemoTest {
|
||||
@Test fun `runs trader demo`() {
|
||||
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 (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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String>) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<TraderDemoClientApi>()
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.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<String, Set<String>> = mapOf(
|
||||
SellerFlow::class.java.name to setOf(Party::class.java.name, Amount::class.java.name)
|
||||
|
Loading…
x
Reference in New Issue
Block a user