Simple Corda RPC Client, getting "Class kotlin.collections.EmptyList is not annotated or on the whitelist, so cannot be used in serialization"


(Curtis Stanford) #1

I have a simple Corda RPC Client running against some nodes I started with the runnodes script. The code is simply:

val client = CordaRPCClient(HostAndPort.fromParts("localhost", 10006))
val connection: CordaRPCConnection by lazy {
    client.start("user1", "test")
}

During the client.start call, I get an exception. Not sure where to whitelist classes on the client side. I assumed there was a default white list that it uses.

com.esotericsoftware.kryo.KryoException: Class kotlin.collections.EmptyList is not annotated or on the whitelist, so cannot be used in serialization
at net.corda.core.serialization.CordaClassResolver.checkClass(CordaClassResolver.kt:65)
at net.corda.core.serialization.CordaClassResolver.getRegistration(CordaClassResolver.kt:35)
at com.esotericsoftware.kryo.Kryo.getRegistration(Kryo.java:488)
at net.corda.nodeapi.RPCKryo.getRegistration(RPCStructures.kt:74)
at com.esotericsoftware.kryo.util.DefaultClassResolver.writeClass(DefaultClassResolver.java:97)
at com.esotericsoftware.kryo.Kryo.writeClass(Kryo.java:540)
at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:645)
at net.corda.core.serialization.KryoKt.serialize(Kryo.kt:169)
at net.corda.core.serialization.KryoKt$serialize$1.execute(Kryo.kt:151)
at net.corda.core.serialization.KryoKt$serialize$1.execute(Kryo.kt)
at com.esotericsoftware.kryo.pool.KryoPoolQueueImpl.run(KryoPoolQueueImpl.java:61)
at net.corda.core.serialization.KryoKt.serialize(Kryo.kt:151)
at net.corda.core.serialization.KryoKt.serialize$default(Kryo.kt:150)
at net.corda.nodeapi.RPCApi$ClientToServer$RpcRequest.writeToClientMessage(RPCApi.kt:103)
at net.corda.client.rpc.internal.RPCClientProxyHandler.invoke(RPCClientProxyHandler.kt:197)
at com.sun.proxy.$Proxy70.getProtocolVersion(Unknown Source)
at net.corda.client.rpc.internal.RPCClient$start$1.invoke(RPCClient.kt:156)
at net.corda.client.rpc.internal.RPCClient$start$1.invoke(RPCClient.kt:86)
at net.corda.core.Utils.logElapsedTime(Utils.kt:230)
at net.corda.core.Utils.logElapsedTime(Utils.kt:240)
at net.corda.client.rpc.internal.RPCClient.start(RPCClient.kt:138)
at net.corda.client.rpc.CordaRPCClient.start(CordaRPCClient.kt:46)
at com.g1.rdl.module.corda.CordaConnection$connection$2.invoke(CordaConnection.kt:12)
at com.g1.rdl.module.corda.CordaConnection$connection$2.invoke(CordaConnection.kt:9)
at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:130)
at com.g1.rdl.module.corda.CordaConnection.getConnection(CordaConnection.kt)
at com.g1.rdl.module.controllers.MainController.onViewLoad(MainController.kt:27)
at io.vavr.control.Try.run(Try.java:105)
at com.g1.el.user.domain.UserJavaClassFile.lambda$onViewLoadAfterFXMLLoad$5(UserJavaClassFile.java:59)
at io.vavr.control.Try.flatMapTry(Try.java:459)
at io.vavr.control.Try.flatMap(Try.java:441)
at com.g1.el.user.domain.UserJavaClassFile.onViewLoadAfterFXMLLoad(UserJavaClassFile.java:59)
at com.g1.el.user.domain.UserView.lambda$onViewLoadAfterFXMLLoad$10(UserView.java:78)
at io.vavr.control.Try.flatMapTry(Try.java:459)
at io.vavr.control.Try.flatMap(Try.java:441)
at com.g1.el.user.domain.UserView.onViewLoadAfterFXMLLoad(UserView.java:78)
at com.g1.el.user.domain.UserView.loadView(UserView.java:109)
at com.g1.el.user.gui.controllers.ElementsViewerPane.<init>(ElementsViewerPane.java:50)
at com.g1.el.user.gui.controllers.ElementsViewOnePane.getElementViewer(ElementsViewOnePane.java:77)
at com.g1.el.user.gui.controllers.ElementsViewOnePane.refresh(ElementsViewOnePane.java:186)
at com.g1.el.user.gui.controllers.ElementsViewOnePane.lambda$new$0(ElementsViewOnePane.java:35)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:105)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at com.g1.el.user.gui.controllers.ElementsViewPane.lambda$new$1(ElementsViewPane.java:49)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:105)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at com.g1.el.user.gui.controllers.ElementsViewPane.lambda$new$5(ElementsViewPane.java:72)
at org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134)
at org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
at org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
at org.reactfx.ProperEventStream.emit(ProperEventStream.java:18)
at org.reactfx.EventStreams$3.lambda$observeInputs$0(EventStreams.java:105)
at org.reactfx.value.ChangeListenerWrapper.accept(Val.java:786)
at org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248)
at org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
at org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
at org.reactfx.value.ValBase.invalidate(ValBase.java:32)
at org.reactfx.value.MappedVal.lambda$connect$0(MappedVal.java:28)
at org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765)
at org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248)
at org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
at org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
at org.reactfx.value.ValBase.invalidate(ValBase.java:32)
at org.reactfx.value.SimpleVar.setValue(SimpleVar.java:59)
at com.g1.el.user.net.syndna.UserConfig.reloadModule(UserConfig.java:108)
at com.g1.el.user.gui.controllers.UserMainController.reloadModule(UserMainController.java:363)
at com.sun.javafx.scene.KeyboardShortcutsHandler.processAccelerators(KeyboardShortcutsHandler.java:347)
at com.sun.javafx.scene.KeyboardShortcutsHandler.dispatchBubblingEvent(KeyboardShortcutsHandler.java:163)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:248)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
at com.sun.glass.ui.View.notifyKey(View.java:966)

(Curtis Stanford) #2

BTW, using version 0.13.0


(Curtis Stanford) #3

I think it has something to do with a URLClassLoader I’m using. It works with a simple main method.


(Mike Hearn) #5

That looks like a bug. I expect it’s taking the “arguments == null” path on line 193 of RPCClientProxyHandler.kt

However, what’s not clear to me is why the classloader thing makes the difference? Perhaps it’s a red herring. Something is different about how the JVM is behaving in your case.

I have filed the list issue here:

https://github.com/corda/corda/issues/994


(Curtis Stanford) #6

I’ve tracked it down to my class loader architecture. I don’t think it’s a bug. The thread running the client rpc connection has a context class loader that doesn’t include the corda jar file that has the service loader file indicating to use the default white list. See DefaultKryoCustomizer, line 43. When I run the client RPC code outside my app, it works fine.

One solution I can think of is to call ServiceLoader.load with a class loader as the second argument. Pass in the class loader associated with the class itself instead of relying on the default behaviour of using the thread context class loader.


(Mike Hearn) #7

Thanks. If you could submit a short PR to make that adjustment it’d be helpful.

I ended up concluding that we probably shouldn’t be serialising Kotlin-specific classes over RPC anyway. The JDK has types we can use here instead that are just as good.


(Curtis Stanford) #8

PR has been submitted:

https://github.com/corda/corda/pull/1020