mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
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:
commit
f30c6950f4
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user