Skip to content

Commit

Permalink
Merged in parkri-extensible-rpc-kryo-types (pull request corda#492)
Browse files Browse the repository at this point in the history
RPC Kryo plugin functionality.
  • Loading branch information
rick-r3 committed Nov 18, 2016
2 parents e5d7f3f + 6abb575 commit e65b2f0
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 5 deletions.
14 changes: 14 additions & 0 deletions core/src/main/kotlin/net/corda/core/node/CordaPluginRegistry.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.corda.core.node

import com.esotericsoftware.kryo.Kryo

/**
* Implement this interface on a class advertised in a META-INF/services/net.corda.core.node.CordaPluginRegistry file
* to extend a Corda node with additional application services.
Expand Down Expand Up @@ -35,4 +37,16 @@ abstract class CordaPluginRegistry {
* allow access to the protocol factory and protocol initiation entry points there.
*/
open val servicePlugins: List<Class<*>> = emptyList()

/**
* Optionally register types with [Kryo] for use over RPC, as we lock down the types that can be serialised in this
* particular use case.
* For example, if you add an RPC interface that carries some contract states back and forth, you need to register
* those classes here using the [register] method on Kryo.
*
* TODO: Kryo and likely the requirement to register classes here will go away when we replace the serialization implementation.
*
* @return true if you register types, otherwise you will be filtered out of the list of plugins considered in future.
*/
open fun registerRPCKryoTypes(kryo: Kryo): Boolean = false
}
12 changes: 12 additions & 0 deletions docs/source/clientrpc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,17 @@ Wire protocol
The client RPC wire protocol is not currently documented. To use it you must use the client library provided.
This is likely to change in a future release.

Registering Classes With RPC Kryo
---------------------------------

In the present implementation of the node we use Kryo to generate the *on the wire* representation of contracts states
or any other classes that form part of the RPC arguments or response. To avoid the RPC interface being wide open to all
classes on the classpath, Cordapps will currently have to register any classes or custom serialisers they require with Kryo
if they are not one of those registered by default in ``RPCKryo`` via the plugin architecture. See :doc:`creating-a-cordapp`.
This will require some familiarity with Kryo. An example is shown in :doc:`tutorial-clientrpc-api`.

.. warning:: We will be replacing the use of Kryo in RPC with a stable message format and this will mean that this plugin
customisation point will either go away completely or change.

.. _CordaRPCClient: api/net.corda.client/-corda-r-p-c-client/index.html
.. _CordaRPCOps: api/net.corda.node.services.messaging/-corda-r-p-c-ops/index.html
1 change: 1 addition & 0 deletions docs/source/creating-a-cordapp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ specific details of the implementation, but you can extend the server in the fol
2. Service plugins: Register your services (see below).
3. Web APIs: You may register your own endpoints under /api/ of the built-in web server.
4. Static web endpoints: You may register your own static serving directories for serving web content.
5. Registering your additional classes used in RPC.

Services
--------
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package net.corda.docs

import com.esotericsoftware.kryo.Kryo
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.CordaPluginRegistry
import net.corda.core.node.services.ServiceInfo
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
Expand Down Expand Up @@ -135,4 +137,17 @@ fun generateTransactions(proxy: CordaRPCOps) {
}
}
}
// END 6
// END 6

// START 7
data class ExampleRPCValue(val foo: String)

class ExampleRPCCordaPluginRegistry : CordaPluginRegistry() {
override fun registerRPCKryoTypes(kryo: Kryo): Boolean {
// Add classes like this.
kryo.register(ExampleRPCValue::class.java)
// You should return true, otherwise your plugin will be ignored for registering classes with Kryo.
return true
}
}
// END 7
16 changes: 16 additions & 0 deletions docs/source/tutorial-clientrpc-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,19 @@ Now let's try to visualise the transaction graph. We will use a graph drawing li
:end-before: END 5

If we run the client with ``Visualise`` we should see a simple random graph being drawn as new transactions are being created.

Registering classes from your Cordapp with RPC Kryo
--------------------------------------------------

As described in :doc:`clientrpc`, you currently have to register any additional classes you add that are needed in RPC
requests or responses with the `Kryo` instance RPC uses. Here's an example of how you do this for an example class.

.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 7
:end-before: END 7

See more on plugins in :doc:`creating-a-cordapp`.

.. warning:: We will be replacing the use of Kryo in RPC with a stable message format and this will mean that this plugin
customisation point will either go away completely or change.
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.Party
import net.corda.core.crypto.PublicKeyTree
import net.corda.core.crypto.SecureHash
import net.corda.core.node.NodeInfo
import net.corda.core.node.PhysicalLocation
import net.corda.core.node.ServiceEntry
import net.corda.core.node.WorldCoordinate
import net.corda.core.node.*
import net.corda.core.node.services.*
import net.corda.core.protocols.StateMachineRunId
import net.corda.core.serialization.*
Expand Down Expand Up @@ -117,6 +114,14 @@ class PermissionException(msg: String) : RuntimeException(msg)
// This is annoying to write out, but will make it easier to formalise the wire protocol when the time comes,
// because we can see everything we're using in one place.
private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null) : Kryo() {
companion object {
private val pluginRegistries: List<CordaPluginRegistry> by lazy {
val unusedKryo = Kryo()
// Sorting required to give a stable ordering, as Kryo allocates integer tokens for each registered class.
ServiceLoader.load(CordaPluginRegistry::class.java).toList().filter { it.registerRPCKryoTypes(unusedKryo) }.sortedBy { it.javaClass.name }
}
}

init {
isRegistrationRequired = true
// Allow construction of objects using a JVM backdoor that skips invoking the constructors, if there is no
Expand Down Expand Up @@ -215,6 +220,7 @@ private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null)
register(ProtocolHandle::class.java)
register(KryoException::class.java)
register(StringBuffer::class.java)
pluginRegistries.forEach { it.registerRPCKryoTypes(this) }
}

// Helper method, attempt to reduce boiler plate code
Expand Down

0 comments on commit e65b2f0

Please sign in to comment.