mirror of
https://github.com/corda/corda.git
synced 2025-05-28 21:24:24 +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
|
package net.corda.notarydemo
|
||||||
|
|
||||||
|
import net.corda.core.div
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
|
import net.corda.node.services.User
|
||||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
/** Creates and starts all nodes required for the demo. */
|
/** Creates and starts all nodes required for the demo. */
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
driver(dsl = {
|
val demoUser = listOf(User("demo", "demo", setOf("StartFlow.net.corda.notarydemo.flows.DummyIssueAndMove", "StartFlow.net.corda.flows.NotaryFlow\$Client")))
|
||||||
startNode("Party")
|
driver(isDebug = true, driverDirectory = Paths.get("build") / "notary-demo-nodes") {
|
||||||
|
startNode("Party", rpcUsers = demoUser)
|
||||||
startNode("Counterparty")
|
startNode("Counterparty")
|
||||||
startNotaryCluster("Raft notary", clusterSize = 3, type = RaftValidatingNotaryService.type)
|
startNotaryCluster("Raft notary", clusterSize = 3, type = RaftValidatingNotaryService.type)
|
||||||
waitForAllNodesToFinish()
|
waitForAllNodesToFinish()
|
||||||
}, isDebug = true)
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,99 @@
|
|||||||
package net.corda.notarydemo
|
package net.corda.notarydemo
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import okhttp3.OkHttpClient
|
import net.corda.core.crypto.toStringShort
|
||||||
import okhttp3.Request
|
import net.corda.core.div
|
||||||
import java.util.concurrent.TimeUnit
|
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>) {
|
fun main(args: Array<String>) {
|
||||||
val api = NotaryDemoClientApi(HostAndPort.fromString("localhost:10003"))
|
val host = HostAndPort.fromString("localhost:10002")
|
||||||
api.startNotarisation()
|
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. */
|
/** Interface for using the notary demo API from a client. */
|
||||||
private class NotaryDemoClientApi(val hostAndPort: HostAndPort) {
|
private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
|
||||||
companion object {
|
|
||||||
private val API_ROOT = "api/notarydemo"
|
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
|
private val TRANSACTION_COUNT = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Makes a call to the demo api to start transaction notarisation. */
|
/** Makes a call to the demo api to start transaction notarisation. */
|
||||||
fun startNotarisation() {
|
fun startNotarisation() {
|
||||||
val request = buildRequest()
|
val response = notarise(TRANSACTION_COUNT)
|
||||||
val response = buildClient().newCall(request).execute()
|
println(response)
|
||||||
println(response.body().string())
|
|
||||||
require(response.isSuccessful)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildRequest() = Request.Builder().url("http://$hostAndPort/$API_ROOT/notarise/$TRANSACTION_COUNT").build()
|
fun notarise(count: Int): String {
|
||||||
private fun buildClient() = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
|
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.node.CordaPluginRegistry
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.flows.NotaryFlow
|
import net.corda.flows.NotaryFlow
|
||||||
import net.corda.notarydemo.api.NotaryDemoApi
|
|
||||||
import net.corda.notarydemo.flows.DummyIssueAndMove
|
import net.corda.notarydemo.flows.DummyIssueAndMove
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
class NotaryDemoPlugin : CordaPluginRegistry() {
|
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
|
// A list of protocols that are required for this cordapp
|
||||||
override val requiredFlows = mapOf(
|
override val requiredFlows = mapOf(
|
||||||
NotaryFlow.Client::class.java.name to setOf(SignedTransaction::class.java.name, setOf(Unit).javaClass.name),
|
NotaryFlow.Client::class.java.name to setOf(SignedTransaction::class.java.name, setOf(Unit).javaClass.name),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user