docs: Update Client RPC API tutorial with how to initiate protocols

This commit is contained in:
Andras Slemmer 2016-11-15 12:36:17 +00:00
parent dcd7a8a08a
commit e6f9570fff
3 changed files with 146 additions and 84 deletions

View File

@ -19,6 +19,14 @@ repositories {
}
}
sourceSets {
main {
resources {
srcDir "../../../config/dev"
}
}
}
dependencies {
compile project(':core')
compile project(':client')

View File

@ -1,15 +1,32 @@
package net.corda.docs
import com.google.common.net.HostAndPort
import net.corda.client.CordaRPCClient
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.USD
import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.messaging.CordaRPCOps
import net.corda.node.services.messaging.startProtocol
import net.corda.node.services.startProtocolPermission
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.protocols.CashCommand
import net.corda.protocols.CashProtocol
import org.graphstream.graph.Edge
import org.graphstream.graph.Node
import org.graphstream.graph.implementations.SingleGraph
import org.graphstream.graph.implementations.MultiGraph
import rx.Observable
import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
import java.util.*
import kotlin.concurrent.thread
/**
* This is example code for the Client RPC API tutorial. The START/END comments are important and used by the documentation!
@ -22,61 +39,100 @@ enum class PrintOrVisualise {
}
fun main(args: Array<String>) {
if (args.size < 2) {
throw IllegalArgumentException("Usage: <binary> <node address> [Print|Visualise]")
if (args.size < 1) {
throw IllegalArgumentException("Usage: <binary> [Print|Visualise]")
}
val nodeAddress = HostAndPort.fromString(args[0])
val printOrVisualise = PrintOrVisualise.valueOf(args[1])
val sslConfig = object : NodeSSLConfiguration {
override val certificatesPath = Paths.get("build/trader-demo/buyer/certificates")
override val keyStorePassword = "cordacadevpass"
override val trustStorePassword = "trustpass"
}
// END 1
val printOrVisualise = PrintOrVisualise.valueOf(args[0])
// START 2
val username = System.console().readLine("Enter username: ")
val password = String(System.console().readPassword("Enter password: "))
val client = CordaRPCClient(nodeAddress, sslConfig)
client.start(username, password)
val proxy = client.proxy()
// END 2
val baseDirectory = Paths.get("build/rpc-api-tutorial")
val user = User("user", "password", permissions = setOf(startProtocolPermission<CashProtocol>()))
// START 3
val (transactions: List<SignedTransaction>, futureTransactions: Observable<SignedTransaction>) = proxy.verifiedTransactions()
// END 3
// START 4
when (printOrVisualise) {
PrintOrVisualise.Print -> {
futureTransactions.startWith(transactions).subscribe { transaction ->
println("NODE ${transaction.id}")
transaction.tx.inputs.forEach { input ->
println("EDGE ${input.txhash} ${transaction.id}")
}
}
CompletableFuture<Unit>().get() // block indefinitely
driver(driverDirectory = baseDirectory) {
startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
val node = startNode("Alice", rpcUsers = listOf(user)).get()
val sslConfig = object : NodeSSLConfiguration {
override val certificatesPath = baseDirectory / "Alice" / "certificates"
override val keyStorePassword = "cordacadevpass"
override val trustStorePassword = "trustpass"
}
// END 4
// START 5
PrintOrVisualise.Visualise -> {
val graph = SingleGraph("transactions")
transactions.forEach { transaction ->
graph.addNode<Node>("${transaction.id}")
}
transactions.forEach { transaction ->
transaction.tx.inputs.forEach { ref ->
graph.addEdge<Edge>("$ref", "${ref.txhash}", "${transaction.id}")
// END 1
// START 2
val client = CordaRPCClient(FullNodeConfiguration(node.config).artemisAddress, sslConfig)
client.start("user", "password")
val proxy = client.proxy()
thread {
generateTransactions(proxy)
}
// END 2
// START 3
val (transactions: List<SignedTransaction>, futureTransactions: Observable<SignedTransaction>) = proxy.verifiedTransactions()
// END 3
// START 4
when (printOrVisualise) {
PrintOrVisualise.Print -> {
futureTransactions.startWith(transactions).subscribe { transaction ->
println("NODE ${transaction.id}")
transaction.tx.inputs.forEach { input ->
println("EDGE ${input.txhash} ${transaction.id}")
}
}
}
futureTransactions.subscribe { transaction ->
graph.addNode<Node>("${transaction.id}")
transaction.tx.inputs.forEach { ref ->
graph.addEdge<Edge>("$ref", "${ref.txhash}", "${transaction.id}")
// END 4
// START 5
PrintOrVisualise.Visualise -> {
val graph = MultiGraph("transactions")
transactions.forEach { transaction ->
graph.addNode<Node>("${transaction.id}")
}
transactions.forEach { transaction ->
transaction.tx.inputs.forEach { ref ->
graph.addEdge<Edge>("$ref", "${ref.txhash}", "${transaction.id}")
}
}
futureTransactions.subscribe { transaction ->
graph.addNode<Node>("${transaction.id}")
transaction.tx.inputs.forEach { ref ->
graph.addEdge<Edge>("$ref", "${ref.txhash}", "${transaction.id}")
}
}
graph.display()
}
graph.display()
}
waitForAllNodesToFinish()
}
}
// END 5
// START 6
fun generateTransactions(proxy: CordaRPCOps) {
var ownedQuantity = proxy.vaultAndUpdates().first.fold(0L) { sum, state ->
sum + (state.state.data as Cash.State).amount.quantity
}
val issueRef = OpaqueBytes.of(0)
val notary = proxy.networkMapUpdates().first.first { it.advertisedServices.any { it.info.type.isNotary() } }.notaryIdentity
val me = proxy.nodeIdentity().legalIdentity
val meAndRef = PartyAndReference(me, issueRef)
while (true) {
Thread.sleep(1000)
val random = SplittableRandom()
val n = random.nextDouble()
if (ownedQuantity > 10000 && n > 0.8) {
val quantity = Math.abs(random.nextLong()) % 2000
proxy.startProtocol(::CashProtocol, CashCommand.ExitCash(Amount(quantity, USD), issueRef))
ownedQuantity -= quantity
} else if (ownedQuantity > 1000 && n < 0.7) {
val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000))
proxy.startProtocol(::CashProtocol, CashCommand.PayCash(Amount(quantity, Issued(meAndRef, USD)), me))
} else {
val quantity = Math.abs(random.nextLong() % 1000)
proxy.startProtocol(::CashProtocol, CashCommand.IssueCash(Amount(quantity, USD), issueRef, me, notary))
ownedQuantity += quantity
}
}
}
// END 5
// END 6

View File

@ -1,31 +1,33 @@
.. _graphstream: http://graphstream-project.org/
Client RPC API
==============
Client RPC API Tutorial
=======================
In this tutorial we will build a simple command line utility that connects to a node and dumps the transaction graph to
the standard output. We will then put some simple visualisation on top. For an explanation on how the RPC works see
:doc:`clientrpc`.
In this tutorial we will build a simple command line utility that
connects to a node, creates some Cash transactions and meanwhile dumps
the transaction graph to the standard output. We will then put some
simple visualisation on top. For an explanation on how the RPC works
see :doc:`clientrpc`.
We start off by connecting to the node itself. For the purposes of the tutorial we will run the Trader demo on some
local port and connect to the Buyer side. We will pass in the address as a command line argument. To connect to the node
we also need to access the certificates of the node, we will access the node's ``certificates`` directory directly.
We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary and a node that issues/exits and moves Cash around for herself. To authenticate we will use the certificates of the nodes directly.
.. literalinclude:: example-code/src/main/kotlin/net.corda.docs/ClientRpcTutorial.kt
Note how we configure the node to create a user that has permission to start the CashProtocol.
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 1
:end-before: END 1
Now we can connect to the node itself using a valid RPC login. By default the user `user1` is available with password `test`.
Now we can connect to the node itself using a valid RPC login. We login using the configured user.
.. literalinclude:: example-code/src/main/kotlin/net.corda.docs/ClientRpcTutorial.kt
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 2
:end-before: END 2
``proxy`` now exposes the full RPC interface of the node:
We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``, which exposes the full RPC interface of the node:
.. literalinclude:: ../../node/src/main/kotlin/net.corda.node/services/messaging/CordaRPCOps.kt
.. literalinclude:: ../../node/src/main/kotlin/net/corda/node/services/messaging/CordaRPCOps.kt
:language: kotlin
:start-after: interface CordaRPCOps
:end-before: }
@ -34,7 +36,7 @@ The one we need in order to dump the transaction graph is ``verifiedTransactions
RPC will return a list of transactions and an Observable stream. This is a general pattern, we query some data and the
node will return the current snapshot and future updates done to it.
.. literalinclude:: example-code/src/main/kotlin/net.corda.docs/ClientRpcTutorial.kt
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 3
:end-before: END 3
@ -43,41 +45,37 @@ The graph will be defined by nodes and edges between them. Each node represents
output-input relations. For now let's just print ``NODE <txhash>`` for the former and ``EDGE <txhash> <txhash>`` for the
latter.
.. literalinclude:: example-code/src/main/kotlin/net.corda.docs/ClientRpcTutorial.kt
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 4
:end-before: END 4
Now we can start the trader demo as per described in :doc:`running-the-demos`::
# Build the demo
./gradlew installDist
# Start the buyer
./build/install/r3prototyping/bin/trader-demo --role=BUYER
Now we just need to create the transactions themselves!
In another terminal we can connect to it with our client::
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 6
:end-before: END 6
# Connect to localhost:31337
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial localhost:31337 Print
We utilise several RPC functions here to query things like the notaries in the node cluster or our own vault.
We should see some ``NODE``-s printed. This is because the buyer self-issues some cash for the demo.
Unless we ran the seller before we shouldn't see any ``EDGE``-s because the cash hasn't been spent yet.
Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction.
In another terminal we can now start the seller::
The RPC we need to initiate a Cash transaction is ``startProtocolDynamic`` which may start an arbitrary protocol, given sufficient permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startProtocol`` that type-checks the arguments for us.
# Start sellers in a loop
for i in {0..9} ; do ./build/install/r3prototyping/bin/trader-demo --role=SELLER ; done
Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while listening on successfully created ones, which are dumped to the console. We just need to run it!:
We should start seeing new ``NODE``-s and ``EDGE``-s appearing.
# Build the example
./gradlew docs/source/example-code:installDist
# Start it
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Print
Now let's try to visualise the transaction graph. We will use a graph drawing library called graphstream_
.. literalinclude:: example-code/src/main/kotlin/net.corda.docs/ClientRpcTutorial.kt
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 5
:end-before: END 5
If we run the client with ``Visualise`` we should see a simple graph being drawn as new transactions are being created
by the seller runs.
That's it! We saw how to connect to the node and stream data from it.
If we run the client with ``Visualise`` we should see a simple random graph being drawn as new transactions are being created.