From e6f9570fff56cf8eac0bf020c9f6f69469d2751b Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Tue, 15 Nov 2016 12:36:17 +0000 Subject: [PATCH] docs: Update Client RPC API tutorial with how to initiate protocols --- docs/source/example-code/build.gradle | 8 + .../net/corda/docs/ClientRpcTutorial.kt | 156 ++++++++++++------ docs/source/tutorial-clientrpc-api.rst | 66 ++++---- 3 files changed, 146 insertions(+), 84 deletions(-) diff --git a/docs/source/example-code/build.gradle b/docs/source/example-code/build.gradle index 39aef67819..a5d0733652 100644 --- a/docs/source/example-code/build.gradle +++ b/docs/source/example-code/build.gradle @@ -19,6 +19,14 @@ repositories { } } +sourceSets { + main { + resources { + srcDir "../../../config/dev" + } + } +} + dependencies { compile project(':core') compile project(':client') diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt index 060b2cd2e4..871bd45924 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt @@ -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) { - if (args.size < 2) { - throw IllegalArgumentException("Usage: [Print|Visualise]") + if (args.size < 1) { + throw IllegalArgumentException("Usage: [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())) - // START 3 - val (transactions: List, futureTransactions: Observable) = 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().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("${transaction.id}") - } - transactions.forEach { transaction -> - transaction.tx.inputs.forEach { ref -> - graph.addEdge("$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, futureTransactions: Observable) = 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("${transaction.id}") - transaction.tx.inputs.forEach { ref -> - graph.addEdge("$ref", "${ref.txhash}", "${transaction.id}") + // END 4 + // START 5 + PrintOrVisualise.Visualise -> { + val graph = MultiGraph("transactions") + transactions.forEach { transaction -> + graph.addNode("${transaction.id}") } + transactions.forEach { transaction -> + transaction.tx.inputs.forEach { ref -> + graph.addEdge("$ref", "${ref.txhash}", "${transaction.id}") + } + } + futureTransactions.subscribe { transaction -> + graph.addNode("${transaction.id}") + transaction.tx.inputs.forEach { ref -> + graph.addEdge("$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 \ No newline at end of file diff --git a/docs/source/tutorial-clientrpc-api.rst b/docs/source/tutorial-clientrpc-api.rst index b97c50e67d..c9419f716b 100644 --- a/docs/source/tutorial-clientrpc-api.rst +++ b/docs/source/tutorial-clientrpc-api.rst @@ -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 `` for the former and ``EDGE `` 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.