Merge pull request #163 from corda/clint-notarydemorpc

Notary demo now uses RPC directly instead of using an intermediate webserver.
This commit is contained in:
Clinton 2017-01-19 14:35:45 +00:00 committed by GitHub
commit f30c6950f4
4 changed files with 90 additions and 96 deletions

View File

@ -1,14 +1,18 @@
package net.corda.notarydemo
import net.corda.core.div
import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.transactions.RaftValidatingNotaryService
import java.nio.file.Paths
/** Creates and starts all nodes required for the demo. */
fun main(args: Array<String>) {
driver(dsl = {
startNode("Party")
val demoUser = listOf(User("demo", "demo", setOf("StartFlow.net.corda.notarydemo.flows.DummyIssueAndMove", "StartFlow.net.corda.flows.NotaryFlow\$Client")))
driver(isDebug = true, driverDirectory = Paths.get("build") / "notary-demo-nodes") {
startNode("Party", rpcUsers = demoUser)
startNode("Counterparty")
startNotaryCluster("Raft notary", clusterSize = 3, type = RaftValidatingNotaryService.type)
waitForAllNodesToFinish()
}, isDebug = true)
}
}

View File

@ -1,30 +1,99 @@
package net.corda.notarydemo
import com.google.common.net.HostAndPort
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.TimeUnit
import net.corda.core.crypto.toStringShort
import net.corda.core.div
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.NotaryFlow
import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.notarydemo.flows.DummyIssueAndMove
import java.nio.file.Path
import java.nio.file.Paths
fun main(args: Array<String>) {
val api = NotaryDemoClientApi(HostAndPort.fromString("localhost:10003"))
api.startNotarisation()
val host = HostAndPort.fromString("localhost:10002")
println("Connecting to the recipient node ($host)")
CordaRPCClient(host, sslConfigFor("nodeb", "build/notary-demo-nodes/Party/certificates")).use("demo", "demo") {
val api = NotaryDemoClientApi(this)
api.startNotarisation()
}
}
/** Interface for using the notary demo API from a client. */
private class NotaryDemoClientApi(val hostAndPort: HostAndPort) {
companion object {
private val API_ROOT = "api/notarydemo"
private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
private val notary by lazy {
rpc.networkMapUpdates().first.first { it.advertisedServices.any { it.info.type.isNotary() } }.notaryIdentity
}
private val counterpartyNode by lazy {
rpc.networkMapUpdates().first.first { it.legalIdentity.name == "Counterparty" }
}
private companion object {
private val TRANSACTION_COUNT = 10
}
/** Makes a call to the demo api to start transaction notarisation. */
fun startNotarisation() {
val request = buildRequest()
val response = buildClient().newCall(request).execute()
println(response.body().string())
require(response.isSuccessful)
val response = notarise(TRANSACTION_COUNT)
println(response)
}
private fun buildRequest() = Request.Builder().url("http://$hostAndPort/$API_ROOT/notarise/$TRANSACTION_COUNT").build()
private fun buildClient() = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
fun notarise(count: Int): String {
val transactions = buildTransactions(count)
val signers = notariseTransactions(transactions)
return buildResponse(transactions, signers)
}
/**
* Builds a number of dummy transactions (as specified by [count]). The party first self-issues a state (asset),
* and builds a transaction to transfer the asset to the counterparty. The *move* transaction requires notarisation,
* as it consumes the original asset and creates a copy with the new owner as its output.
*/
private fun buildTransactions(count: Int): List<SignedTransaction> {
val moveTransactions = (1..count).map {
rpc.startFlow(::DummyIssueAndMove, notary, counterpartyNode.legalIdentity).returnValue.toBlocking().toFuture()
}
return moveTransactions.map { it.get() }
}
/**
* For every transaction invoke the notary flow and obtains a notary signature.
* The signer can be any of the nodes in the notary cluster.
*
* @return a list of encoded signer public keys - one for every transaction
*/
private fun notariseTransactions(transactions: List<SignedTransaction>): List<String> {
val signatureFutures = transactions.map {
rpc.startFlow(NotaryFlow::Client, it).returnValue.toBlocking().toFuture()
}
val signers = signatureFutures.map { it.get().by.toStringShort() }
return signers
}
/** Builds a response for the caller containing the list of transaction ids and corresponding signer keys. */
private fun buildResponse(transactions: List<SignedTransaction>, signers: List<String>): String {
val transactionSigners = transactions.zip(signers).map {
val (tx, signer) = it
"Tx [${tx.tx.id.prefixChars()}..] signed by $signer"
}.joinToString("\n")
val response = "Notary: \"${notary.name}\", with composite key: ${notary.owningKey}\n" +
"Notarised ${transactions.size} transactions:\n" + transactionSigners
return response
}
}
// 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"
}
}

View File

@ -1,76 +0,0 @@
package net.corda.notarydemo.api
import net.corda.core.contracts.DummyContract
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.toStringShort
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.node.ServiceHub
import net.corda.core.node.recordTransactions
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.NotaryFlow
import net.corda.notarydemo.flows.DummyIssueAndMove
import java.util.*
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import javax.ws.rs.core.Response
@Path("notarydemo")
class NotaryDemoApi(val rpc: CordaRPCOps) {
private val notary by lazy {
rpc.networkMapUpdates().first.first { it.advertisedServices.any { it.info.type.isNotary() } }.notaryIdentity
}
private val counterpartyNode by lazy {
rpc.networkMapUpdates().first.first { it.legalIdentity.name == "Counterparty" }
}
@GET
@Path("/notarise/{count}")
fun notarise(@PathParam("count") count: Int): Response {
val transactions = buildTransactions(count)
val signers = notariseTransactions(transactions)
val response = buildResponse(transactions, signers)
return Response.ok(response).build()
}
/**
* Builds a number of dummy transactions (as specified by [count]). The party first self-issues a state (asset),
* and builds a transaction to transfer the asset to the counterparty. The *move* transaction requires notarisation,
* as it consumes the original asset and creates a copy with the new owner as its output.
*/
private fun buildTransactions(count: Int): List<SignedTransaction> {
val moveTransactions = (1..count).map {
rpc.startFlow(::DummyIssueAndMove, notary, counterpartyNode.legalIdentity).returnValue.toBlocking().toFuture()
}
return moveTransactions.map { it.get() }
}
/**
* For every transactions invokes the notary flow and obtains a notary signature.
* The signer can be any of the nodes in the notary cluster.
*
* @return a list of encoded signer public keys one for every transaction
*/
private fun notariseTransactions(transactions: List<SignedTransaction>): List<String> {
val signatureFutures = transactions.map {
rpc.startFlow(NotaryFlow::Client, it).returnValue.toBlocking().toFuture()
}
val signers = signatureFutures.map { it.get().by.toStringShort() }
return signers
}
/** Builds a response for the caller containing the list of transaction ids and corresponding signer keys. */
private fun buildResponse(transactions: List<SignedTransaction>, signers: List<String>): String {
val transactionSigners = transactions.zip(signers).map {
val (tx, signer) = it
"Tx [${tx.tx.id.prefixChars()}..] signed by $signer"
}.joinToString("\n")
val response = "Notary: \"${notary.name}\", with composite key: ${notary.owningKey}\n" +
"Notarised ${transactions.size} transactions:\n" + transactionSigners
return response
}
}

View File

@ -4,13 +4,10 @@ import net.corda.core.crypto.Party
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.NotaryFlow
import net.corda.notarydemo.api.NotaryDemoApi
import net.corda.notarydemo.flows.DummyIssueAndMove
import java.util.function.Function
class NotaryDemoPlugin : CordaPluginRegistry() {
// A list of classes that expose web APIs.
override val webApis = listOf(Function(::NotaryDemoApi))
// A list of protocols that are required for this cordapp
override val requiredFlows = mapOf(
NotaryFlow.Client::class.java.name to setOf(SignedTransaction::class.java.name, setOf(Unit).javaClass.name),