Client RPC APIΒΆ

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 Client RPC.

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.

enum class PrintOrVisualise {
    Print,
    Visualise
}

fun main(args: Array<String>) {
    if (args.size < 2) {
        throw IllegalArgumentException("Usage: <binary> <node address> [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"
    }

Now we can connect to the node itself using a valid RPC login. By default the user user1 is available with password test.

    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()

proxy now exposes the full RPC interface of the node:

    /**
     * Returns a pair of currently in-progress state machine infos and an observable of future state machine adds/removes.
     */
    @RPCReturnsObservables
    fun stateMachinesAndUpdates(): Pair<List<StateMachineInfo>, Observable<StateMachineUpdate>>

    /**
     * Returns a pair of head states in the vault and an observable of future updates to the vault.
     */
    @RPCReturnsObservables
    fun vaultAndUpdates(): Pair<List<StateAndRef<ContractState>>, Observable<Vault.Update>>

    /**
     * Returns a pair of all recorded transactions and an observable of future recorded ones.
     */
    @RPCReturnsObservables
    fun verifiedTransactions(): Pair<List<SignedTransaction>, Observable<SignedTransaction>>

    /**
     * Returns a snapshot list of existing state machine id - recorded transaction hash mappings, and a stream of future
     * such mappings as well.
     */
    @RPCReturnsObservables
    fun stateMachineRecordedTransactionMapping(): Pair<List<StateMachineTransactionMapping>, Observable<StateMachineTransactionMapping>>

    /**
     * Returns all parties currently visible on the network with their advertised services and an observable of future updates to the network.
     */
    @RPCReturnsObservables
    fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>>

    /**
     * Executes the given command if the user is permissioned to do so, possibly triggering cash creation etc.
     * TODO: The signature of this is weird because it's the remains of an old service call, we should have a call for each command instead.
     */
    fun executeCommand(command: ClientToServiceCommand): TransactionBuildResult

    /**
     * Returns Node's identity, assuming this will not change while the node is running.
     */
    fun nodeIdentity(): NodeInfo
    /*
     * Add note(s) to an existing Vault transaction
     */
    fun addVaultTransactionNote(txnId: SecureHash, txnNote: String)

    /*
     * Retrieve existing note(s) for a given Vault transaction
     */
    fun getVaultTransactionNotes(txnId: SecureHash): Iterable<String>

The one we need in order to dump the transaction graph is verifiedTransactions. The type signature tells us that 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.

    val (transactions: List<SignedTransaction>, futureTransactions: Observable<SignedTransaction>) = proxy.verifiedTransactions()

The graph will be defined by nodes and edges between them. Each node represents a transaction and edges represent output-input relations. For now let’s just print NODE <txhash> for the former and EDGE <txhash> <txhash> for the latter.

    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
        }

Now we can start the trader demo as per described in Running the demos:

# Build the demo
./gradlew installDist
# Start the buyer
./build/install/r3prototyping/bin/trader-demo --role=BUYER

In another terminal we can connect to it with our client:

# Connect to localhost:31337
./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. 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:

# Start sellers in a loop
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.

Now let’s try to visualise the transaction graph. We will use a graph drawing library called graphstream

        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}")
                }
            }
            futureTransactions.subscribe { transaction ->
                graph.addNode<Node>("${transaction.id}")
                transaction.tx.inputs.forEach { ref ->
                    graph.addEdge<Edge>("$ref", "${ref.txhash}", "${transaction.id}")
                }
            }
            graph.display()
        }
    }
}

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.