mirror of
https://github.com/corda/corda.git
synced 2025-03-25 13:27:58 +00:00
docs: Update Client RPC API tutorial with how to initiate protocols
This commit is contained in:
parent
dcd7a8a08a
commit
e6f9570fff
@ -19,6 +19,14 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDir "../../../config/dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':client')
|
||||
|
@ -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
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user