mirror of
https://github.com/corda/corda.git
synced 2025-06-19 15:43:52 +00:00
docs: Update Client RPC API tutorial with how to initiate protocols
This commit is contained in:
@ -19,6 +19,14 @@ repositories {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
resources {
|
||||||
|
srcDir "../../../config/dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':client')
|
compile project(':client')
|
||||||
|
@ -1,15 +1,32 @@
|
|||||||
package net.corda.docs
|
package net.corda.docs
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
|
||||||
import net.corda.client.CordaRPCClient
|
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.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.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.Edge
|
||||||
import org.graphstream.graph.Node
|
import org.graphstream.graph.Node
|
||||||
import org.graphstream.graph.implementations.SingleGraph
|
import org.graphstream.graph.implementations.MultiGraph
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.nio.file.Paths
|
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!
|
* This is example code for the Client RPC API tutorial. The START/END comments are important and used by the documentation!
|
||||||
@ -22,24 +39,32 @@ enum class PrintOrVisualise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
if (args.size < 2) {
|
if (args.size < 1) {
|
||||||
throw IllegalArgumentException("Usage: <binary> <node address> [Print|Visualise]")
|
throw IllegalArgumentException("Usage: <binary> [Print|Visualise]")
|
||||||
}
|
}
|
||||||
val nodeAddress = HostAndPort.fromString(args[0])
|
val printOrVisualise = PrintOrVisualise.valueOf(args[0])
|
||||||
val printOrVisualise = PrintOrVisualise.valueOf(args[1])
|
|
||||||
|
val baseDirectory = Paths.get("build/rpc-api-tutorial")
|
||||||
|
val user = User("user", "password", permissions = setOf(startProtocolPermission<CashProtocol>()))
|
||||||
|
|
||||||
|
driver(driverDirectory = baseDirectory) {
|
||||||
|
startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
|
||||||
|
val node = startNode("Alice", rpcUsers = listOf(user)).get()
|
||||||
val sslConfig = object : NodeSSLConfiguration {
|
val sslConfig = object : NodeSSLConfiguration {
|
||||||
override val certificatesPath = Paths.get("build/trader-demo/buyer/certificates")
|
override val certificatesPath = baseDirectory / "Alice" / "certificates"
|
||||||
override val keyStorePassword = "cordacadevpass"
|
override val keyStorePassword = "cordacadevpass"
|
||||||
override val trustStorePassword = "trustpass"
|
override val trustStorePassword = "trustpass"
|
||||||
}
|
}
|
||||||
// END 1
|
// END 1
|
||||||
|
|
||||||
// START 2
|
// START 2
|
||||||
val username = System.console().readLine("Enter username: ")
|
val client = CordaRPCClient(FullNodeConfiguration(node.config).artemisAddress, sslConfig)
|
||||||
val password = String(System.console().readPassword("Enter password: "))
|
client.start("user", "password")
|
||||||
val client = CordaRPCClient(nodeAddress, sslConfig)
|
|
||||||
client.start(username, password)
|
|
||||||
val proxy = client.proxy()
|
val proxy = client.proxy()
|
||||||
|
|
||||||
|
thread {
|
||||||
|
generateTransactions(proxy)
|
||||||
|
}
|
||||||
// END 2
|
// END 2
|
||||||
|
|
||||||
// START 3
|
// START 3
|
||||||
@ -55,12 +80,11 @@ fun main(args: Array<String>) {
|
|||||||
println("EDGE ${input.txhash} ${transaction.id}")
|
println("EDGE ${input.txhash} ${transaction.id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompletableFuture<Unit>().get() // block indefinitely
|
|
||||||
}
|
}
|
||||||
// END 4
|
// END 4
|
||||||
// START 5
|
// START 5
|
||||||
PrintOrVisualise.Visualise -> {
|
PrintOrVisualise.Visualise -> {
|
||||||
val graph = SingleGraph("transactions")
|
val graph = MultiGraph("transactions")
|
||||||
transactions.forEach { transaction ->
|
transactions.forEach { transaction ->
|
||||||
graph.addNode<Node>("${transaction.id}")
|
graph.addNode<Node>("${transaction.id}")
|
||||||
}
|
}
|
||||||
@ -78,5 +102,37 @@ fun main(args: Array<String>) {
|
|||||||
graph.display()
|
graph.display()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
waitForAllNodesToFinish()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// END 5
|
// 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 6
|
@ -1,31 +1,33 @@
|
|||||||
.. _graphstream: http://graphstream-project.org/
|
.. _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
|
In this tutorial we will build a simple command line utility that
|
||||||
the standard output. We will then put some simple visualisation on top. For an explanation on how the RPC works see
|
connects to a node, creates some Cash transactions and meanwhile dumps
|
||||||
:doc:`clientrpc`.
|
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
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
.. 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
|
:language: kotlin
|
||||||
:start-after: START 1
|
:start-after: START 1
|
||||||
:end-before: END 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
|
:language: kotlin
|
||||||
:start-after: START 2
|
:start-after: START 2
|
||||||
:end-before: END 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
|
:language: kotlin
|
||||||
:start-after: interface CordaRPCOps
|
:start-after: interface CordaRPCOps
|
||||||
:end-before: }
|
: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
|
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.
|
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
|
:language: kotlin
|
||||||
:start-after: START 3
|
:start-after: START 3
|
||||||
:end-before: END 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
|
output-input relations. For now let's just print ``NODE <txhash>`` for the former and ``EDGE <txhash> <txhash>`` for the
|
||||||
latter.
|
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
|
:language: kotlin
|
||||||
:start-after: START 4
|
:start-after: START 4
|
||||||
:end-before: END 4
|
:end-before: END 4
|
||||||
|
|
||||||
Now we can start the trader demo as per described in :doc:`running-the-demos`::
|
|
||||||
|
|
||||||
# Build the demo
|
Now we just need to create the transactions themselves!
|
||||||
./gradlew installDist
|
|
||||||
# Start the buyer
|
|
||||||
./build/install/r3prototyping/bin/trader-demo --role=BUYER
|
|
||||||
|
|
||||||
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
|
We utilise several RPC functions here to query things like the notaries in the node cluster or our own vault.
|
||||||
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial localhost:31337 Print
|
|
||||||
|
|
||||||
We should see some ``NODE``-s printed. This is because the buyer self-issues some cash for the demo.
|
Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction.
|
||||||
Unless we ran the seller before we shouldn't see any ``EDGE``-s because the cash hasn't been spent yet.
|
|
||||||
|
|
||||||
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
|
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!:
|
||||||
for i in {0..9} ; do ./build/install/r3prototyping/bin/trader-demo --role=SELLER ; done
|
|
||||||
|
|
||||||
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_
|
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
|
:language: kotlin
|
||||||
:start-after: START 5
|
:start-after: START 5
|
||||||
:end-before: END 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
|
If we run the client with ``Visualise`` we should see a simple random 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.
|
|
||||||
|
Reference in New Issue
Block a user