mirror of
https://github.com/corda/corda.git
synced 2025-01-19 03:06:36 +00:00
CORDA-1264: Mask internal errors if devMode is false (#3942)
This commit is contained in:
parent
df4699c69a
commit
c79dd8017d
@ -21,13 +21,13 @@ import net.corda.core.CordaRuntimeException
|
||||
* the exception is handled. This ID is propagated to counterparty flows, even when the [FlowException] is
|
||||
* downgraded to an [UnexpectedFlowEndException]. This is so the error conditions may be correlated later on.
|
||||
*/
|
||||
open class FlowException(message: String?, cause: Throwable?) :
|
||||
open class FlowException(message: String?, cause: Throwable?, var originalErrorId: Long? = null) :
|
||||
CordaException(message, cause), IdentifiableException {
|
||||
constructor(message: String?, cause: Throwable?) : this(message, cause, null)
|
||||
constructor(message: String?) : this(message, null)
|
||||
constructor(cause: Throwable?) : this(cause?.toString(), cause)
|
||||
constructor() : this(null, null)
|
||||
|
||||
var originalErrorId: Long? = null
|
||||
override fun getErrorId(): Long? = originalErrorId
|
||||
}
|
||||
// DOCEND 1
|
||||
|
@ -105,6 +105,9 @@ Unreleased
|
||||
* ``WireTransaction.Companion.createComponentGroups`` has been marked as ``@CordaInternal``. It was never intended to be
|
||||
public and was already internal for Kotlin code.
|
||||
|
||||
* RPC server will now mask internal errors to RPC clients if not in devMode. ``Throwable``s implementing ``ClientRelevantError``
|
||||
will continue to be propagated to clients.
|
||||
|
||||
* RPC Framework moved from Kryo to the Corda AMQP implementation [Corda-847]. This completes the removal
|
||||
of ``Kryo`` from general use within Corda, remaining only for use in flow checkpointing.
|
||||
|
||||
@ -217,7 +220,7 @@ Version 3.1
|
||||
* Update the fast-classpath-scanner dependent library version from 2.0.21 to 2.12.3
|
||||
|
||||
.. note:: Whilst this is not the latest version of this library, that being 2.18.1 at time of writing, versions
|
||||
later than 2.12.3 (including 2.12.4) exhibit a different issue.
|
||||
later than 2.12.3 (including 2.12.4) exhibit a different issue.
|
||||
|
||||
* Updated the api scanner gradle plugin to work the same way as the version in master. These changes make the api scanner more
|
||||
accurate and fix a couple of bugs, and change the format of the api-current.txt file slightly. Backporting these changes
|
||||
@ -1035,15 +1038,15 @@ Special thank you to `Qian Hong <https://github.com/fracting>`_, `Marek Skocovsk
|
||||
to Corda in M10.
|
||||
|
||||
.. warning:: Due to incompatibility between older version of IntelliJ and gradle 3.4, you will need to upgrade Intellij
|
||||
to 2017.1 (with kotlin-plugin v1.1.1) in order to run Corda demos in IntelliJ. You can download the latest IntelliJ
|
||||
to 2017.1 (with kotlin-plugin v1.1.1) in order to run Corda demos in IntelliJ. You can download the latest IntelliJ
|
||||
from `JetBrains <https://www.jetbrains.com/idea/download/>`_.
|
||||
|
||||
.. warning:: The Kapt-generated models are no longer included in our codebase. If you experience ``unresolved references``
|
||||
errors when building in IntelliJ, please rebuild the schema model by running ``gradlew kaptKotlin`` in Windows or
|
||||
errors when building in IntelliJ, please rebuild the schema model by running ``gradlew kaptKotlin`` in Windows or
|
||||
``./gradlew kaptKotlin`` in other systems. Alternatively, perform a full gradle build or install.
|
||||
|
||||
.. note:: Kapt is used to generate schema model and entity code (from annotations in the codebase) using the Kotlin Annotation
|
||||
processor.
|
||||
processor.
|
||||
|
||||
* Corda DemoBench:
|
||||
* DemoBench is a new tool to make it easy to configure and launch local Corda nodes. A very useful tool to demonstrate
|
||||
|
@ -338,9 +338,15 @@ Error handling
|
||||
--------------
|
||||
If something goes wrong with the RPC infrastructure itself, an ``RPCException`` is thrown. If you call a method that
|
||||
requires a higher version of the protocol than the server supports, ``UnsupportedOperationException`` is thrown.
|
||||
Otherwise, if the server implementation throws an exception, that exception is serialised and rethrown on the client
|
||||
Otherwise the behaviour depends on the ``devMode`` node configuration option.
|
||||
|
||||
In ``devMode``, if the server implementation throws an exception, that exception is serialised and rethrown on the client
|
||||
side as if it was thrown from inside the called RPC method. These exceptions can be caught as normal.
|
||||
|
||||
When not in ``devMode``, the server will mask exceptions not meant for clients and return an ``InternalNodeException`` instead.
|
||||
This does not expose internal information to clients, strengthening privacy and security. CorDapps can have exceptions implement
|
||||
``ClientRelevantError`` to allow them to reach RPC clients.
|
||||
|
||||
Connection management
|
||||
---------------------
|
||||
It is possible to not be able to connect to the server on the first attempt. In that case, the ``CordaRPCClient.start()``
|
||||
|
@ -12,10 +12,14 @@ import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.NodeParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.startNode
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Test
|
||||
import java.io.*
|
||||
@ -24,12 +28,21 @@ import kotlin.test.assertEquals
|
||||
class BootTests {
|
||||
@Test
|
||||
fun `java deserialization is disabled`() {
|
||||
driver(DriverParameters(notarySpecs = emptyList())) {
|
||||
val user = User("u", "p", setOf(startFlow<ObjectInputStreamFlow>()))
|
||||
val future = CordaRPCClient(startNode(rpcUsers = listOf(user)).getOrThrow().rpcAddress).
|
||||
start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue
|
||||
assertThatThrownBy { future.getOrThrow() }
|
||||
.isInstanceOf(CordaRuntimeException::class.java)
|
||||
val user = User("u", "p", setOf(startFlow<ObjectInputStreamFlow>()))
|
||||
val params = NodeParameters(rpcUsers = listOf(user))
|
||||
|
||||
fun NodeHandle.attemptJavaDeserialization() {
|
||||
CordaRPCClient(rpcAddress).use(user.username, user.password) { connection ->
|
||||
connection.proxy
|
||||
rpc.startFlow(::ObjectInputStreamFlow).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
driver {
|
||||
val devModeNode = startNode(params).getOrThrow()
|
||||
val node = startNode(ALICE_NAME, devMode = false, parameters = params).getOrThrow()
|
||||
|
||||
assertThatThrownBy { devModeNode.attemptJavaDeserialization() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||
assertThatThrownBy { node.attemptJavaDeserialization() }.isInstanceOf(InternalNodeException::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.messaging.ParametersUpdateInfo
|
||||
@ -10,7 +8,6 @@ import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.config.configureDevKeyAndTrustStores
|
||||
import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||
@ -20,7 +17,6 @@ import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
||||
import net.corda.testing.node.internal.*
|
||||
import net.corda.testing.node.internal.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -242,16 +238,3 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP
|
||||
assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DriverDSLImpl.startNode(providedName: CordaX500Name, devMode: Boolean): CordaFuture<NodeHandle> {
|
||||
var customOverrides = emptyMap<String, String>()
|
||||
if (!devMode) {
|
||||
val nodeDir = baseDirectory(providedName)
|
||||
val certificatesDirectory = nodeDir / "certificates"
|
||||
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
|
||||
val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
|
||||
p2pSslConfig.configureDevKeyAndTrustStores(providedName, signingCertStore, certificatesDirectory)
|
||||
customOverrides = mapOf("devMode" to "false")
|
||||
}
|
||||
return startNode(providedName = providedName, customOverrides = customOverrides)
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.NodeParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.*
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.startNode
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
|
||||
import org.hibernate.exception.GenericJDBCException
|
||||
@ -23,75 +23,95 @@ import org.junit.Test
|
||||
import java.sql.SQLException
|
||||
|
||||
class RpcExceptionHandlingTest {
|
||||
|
||||
private val user = User("mark", "dadada", setOf(Permissions.all()))
|
||||
private val users = listOf(user)
|
||||
|
||||
@Test
|
||||
fun `rpc client handles exceptions thrown on node side`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
fun `rpc client receive client-relevant exceptions regardless of devMode`() {
|
||||
val params = NodeParameters(rpcUsers = users)
|
||||
val clientRelevantMessage = "This is for the players!"
|
||||
|
||||
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||
|
||||
assertThatThrownBy { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
|
||||
|
||||
assertThat(exception).hasNoCause()
|
||||
assertThat(exception.stackTrace).isEmpty()
|
||||
}
|
||||
fun NodeHandle.throwExceptionFromFlow() {
|
||||
rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rpc client handles client-relevant exceptions thrown on node side`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
|
||||
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||
val clientRelevantMessage = "This is for the players!"
|
||||
|
||||
assertThatThrownBy { node.rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow() }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
|
||||
fun assertThatThrownExceptionIsReceivedUnwrapped(node: NodeHandle) {
|
||||
assertThatThrownBy { node.throwExceptionFromFlow() }.isInstanceOfSatisfying(ClientRelevantException::class.java) { exception ->
|
||||
|
||||
assertThat(exception).hasNoCause()
|
||||
assertThat(exception.stackTrace).isEmpty()
|
||||
assertThat(exception.message).isEqualTo(clientRelevantMessage)
|
||||
}
|
||||
}
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
val devModeNode = startNode(params, BOB_NAME).getOrThrow()
|
||||
val node = startNode(ALICE_NAME, devMode = false, parameters = params).getOrThrow()
|
||||
|
||||
assertThatThrownExceptionIsReceivedUnwrapped(devModeNode)
|
||||
assertThatThrownExceptionIsReceivedUnwrapped(node)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `FlowException is received by the RPC client`() {
|
||||
fun `FlowException is received by the RPC client only if in devMode`() {
|
||||
val params = NodeParameters(rpcUsers = users)
|
||||
val expectedMessage = "Flow error!"
|
||||
val expectedErrorId = 123L
|
||||
|
||||
fun NodeHandle.throwExceptionFromFlow() {
|
||||
rpc.startFlow(::FlowExceptionFlow, expectedMessage, expectedErrorId).returnValue.getOrThrow()
|
||||
}
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||
val exceptionMessage = "Flow error!"
|
||||
assertThatThrownBy { node.rpc.startFlow(::FlowExceptionFlow, exceptionMessage).returnValue.getOrThrow() }
|
||||
.isInstanceOfSatisfying(FlowException::class.java) { exception ->
|
||||
assertThat(exception).hasNoCause()
|
||||
assertThat(exception.stackTrace).isEmpty()
|
||||
assertThat(exception.message).isEqualTo(exceptionMessage)
|
||||
}
|
||||
val devModeNode = startNode(params, BOB_NAME).getOrThrow()
|
||||
val node = startNode(ALICE_NAME, devMode = false, parameters = params).getOrThrow()
|
||||
|
||||
assertThatThrownBy { devModeNode.throwExceptionFromFlow() }.isInstanceOfSatisfying(FlowException::class.java) { exception ->
|
||||
|
||||
assertThat(exception).hasNoCause()
|
||||
assertThat(exception.stackTrace).isEmpty()
|
||||
assertThat(exception.message).isEqualTo(expectedMessage)
|
||||
assertThat(exception.errorId).isEqualTo(expectedErrorId)
|
||||
}
|
||||
assertThatThrownBy { node.throwExceptionFromFlow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||
|
||||
assertThat(exception).hasNoCause()
|
||||
assertThat(exception.stackTrace).isEmpty()
|
||||
assertThat(exception.message).isEqualTo(InternalNodeException.message)
|
||||
assertThat(exception.errorId).isEqualTo(expectedErrorId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rpc client handles exceptions thrown on counter-party side`() {
|
||||
val params = NodeParameters(rpcUsers = users)
|
||||
|
||||
fun DriverDSL.scenario(devMode: Boolean) {
|
||||
|
||||
val nodeA = startNode(ALICE_NAME, devMode, params).getOrThrow()
|
||||
val nodeB = startNode(BOB_NAME, devMode, params).getOrThrow()
|
||||
|
||||
nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow()
|
||||
}
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
|
||||
val nodeA = startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = users)).getOrThrow()
|
||||
val nodeB = startNode(NodeParameters(providedName = BOB_NAME, rpcUsers = users)).getOrThrow()
|
||||
|
||||
assertThatThrownBy { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
|
||||
assertThatThrownBy { scenario(true) }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
|
||||
|
||||
assertThat(exception).hasNoCause()
|
||||
assertThat(exception.stackTrace).isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
assertThatThrownBy { scenario(false) }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||
|
||||
@StartableByRPC
|
||||
class Flow : FlowLogic<String>() {
|
||||
@Suspendable
|
||||
override fun call(): String {
|
||||
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
|
||||
assertThat(exception).hasNoCause()
|
||||
assertThat(exception.stackTrace).isEmpty()
|
||||
assertThat(exception.message).isEqualTo(InternalNodeException.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +141,11 @@ class ClientRelevantErrorFlow(private val message: String) : FlowLogic<String>()
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
class FlowExceptionFlow(private val message: String) : FlowLogic<String>() {
|
||||
class FlowExceptionFlow(private val message: String, private val errorId: Long? = null) : FlowLogic<String>() {
|
||||
@Suspendable
|
||||
override fun call(): String = throw FlowException(message)
|
||||
override fun call(): String {
|
||||
val exception = FlowException(message)
|
||||
errorId?.let { exception.originalErrorId = it }
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import net.corda.node.internal.cordapp.CordappConfigFileProvider
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.node.internal.cordapp.CordappProviderInternal
|
||||
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
|
||||
import net.corda.node.internal.rpc.proxies.ExceptionMaskingRpcOpsProxy
|
||||
import net.corda.node.internal.rpc.proxies.ExceptionSerialisingRpcOpsProxy
|
||||
import net.corda.node.services.ContractUpgradeHandler
|
||||
import net.corda.node.services.FinalityHandler
|
||||
@ -238,8 +239,13 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
||||
open fun makeRPCOps(): CordaRPCOps {
|
||||
val ops: CordaRPCOps = CordaRPCOpsImpl(services, smm, flowStarter) { shutdownExecutor.submit { stop() } }
|
||||
val proxies = mutableListOf<(CordaRPCOps) -> CordaRPCOps>()
|
||||
// Mind that order is relevant here.
|
||||
val proxies = listOf<(CordaRPCOps) -> CordaRPCOps>(::AuthenticatedRpcOpsProxy, { ExceptionSerialisingRpcOpsProxy(it, true) })
|
||||
proxies += ::AuthenticatedRpcOpsProxy
|
||||
if (!configuration.devMode) {
|
||||
proxies += { it -> ExceptionMaskingRpcOpsProxy(it, true) }
|
||||
}
|
||||
proxies += { it -> ExceptionSerialisingRpcOpsProxy(it, configuration.devMode) }
|
||||
return proxies.fold(ops) { delegate, decorate -> decorate(delegate) }
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,125 @@
|
||||
package net.corda.node.internal.rpc.proxies
|
||||
|
||||
import net.corda.core.ClientRelevantError
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.doOnError
|
||||
import net.corda.core.flows.IdentifiableException
|
||||
import net.corda.core.internal.concurrent.doOnError
|
||||
import net.corda.core.internal.concurrent.mapError
|
||||
import net.corda.core.internal.declaredField
|
||||
import net.corda.core.mapErrors
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.FlowHandleImpl
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
import net.corda.core.messaging.FlowProgressHandleImpl
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.internal.InvocationHandlerTemplate
|
||||
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||
import rx.Observable
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy.newProxyInstance
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal class ExceptionMaskingRpcOpsProxy(private val delegate: CordaRPCOps, doLog: Boolean) : CordaRPCOps by proxy(delegate, doLog) {
|
||||
private companion object {
|
||||
private val logger = loggerFor<ExceptionMaskingRpcOpsProxy>()
|
||||
|
||||
private val whitelist = setOf(
|
||||
ClientRelevantError::class,
|
||||
TransactionVerificationException::class
|
||||
)
|
||||
|
||||
private fun proxy(delegate: CordaRPCOps, doLog: Boolean): CordaRPCOps {
|
||||
val handler = ErrorObfuscatingInvocationHandler(delegate, whitelist, doLog)
|
||||
return newProxyInstance(delegate::class.java.classLoader, arrayOf(CordaRPCOps::class.java), handler) as CordaRPCOps
|
||||
}
|
||||
}
|
||||
|
||||
private class ErrorObfuscatingInvocationHandler(override val delegate: CordaRPCOps, private val whitelist: Set<KClass<*>>, private val doLog: Boolean) : InvocationHandlerTemplate {
|
||||
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? {
|
||||
try {
|
||||
val result = super.invoke(proxy, method, arguments)
|
||||
return result?.let { obfuscateResult(it) }
|
||||
} catch (exception: Exception) {
|
||||
// In this special case logging and re-throwing is the right approach.
|
||||
log(exception)
|
||||
throw obfuscate(exception)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <RESULT : Any> obfuscateResult(result: RESULT): Any {
|
||||
return when (result) {
|
||||
is CordaFuture<*> -> wrapFuture(result)
|
||||
is DataFeed<*, *> -> wrapFeed(result)
|
||||
is FlowProgressHandle<*> -> wrapFlowProgressHandle(result)
|
||||
is FlowHandle<*> -> wrapFlowHandle(result)
|
||||
is Observable<*> -> wrapObservable(result)
|
||||
else -> result
|
||||
}
|
||||
}
|
||||
|
||||
private fun wrapFlowProgressHandle(handle: FlowProgressHandle<*>): FlowProgressHandle<*> {
|
||||
val returnValue = wrapFuture(handle.returnValue)
|
||||
val progress = wrapObservable(handle.progress)
|
||||
val stepsTreeIndexFeed = handle.stepsTreeIndexFeed?.let { wrapFeed(it) }
|
||||
val stepsTreeFeed = handle.stepsTreeFeed?.let { wrapFeed(it) }
|
||||
|
||||
return FlowProgressHandleImpl(handle.id, returnValue, progress, stepsTreeIndexFeed, stepsTreeFeed)
|
||||
}
|
||||
|
||||
private fun wrapFlowHandle(handle: FlowHandle<*>): FlowHandle<*> {
|
||||
return FlowHandleImpl(handle.id, wrapFuture(handle.returnValue))
|
||||
}
|
||||
|
||||
private fun <ELEMENT> wrapObservable(observable: Observable<ELEMENT>): Observable<ELEMENT> {
|
||||
return observable.doOnError(::log).mapErrors(::obfuscate)
|
||||
}
|
||||
|
||||
private fun <SNAPSHOT, ELEMENT> wrapFeed(feed: DataFeed<SNAPSHOT, ELEMENT>): DataFeed<SNAPSHOT, ELEMENT> {
|
||||
return feed.doOnError(::log).mapErrors(::obfuscate)
|
||||
}
|
||||
|
||||
private fun <RESULT> wrapFuture(future: CordaFuture<RESULT>): CordaFuture<RESULT> {
|
||||
return future.doOnError(::log).mapError(::obfuscate)
|
||||
}
|
||||
|
||||
private fun log(error: Throwable) {
|
||||
if (doLog) {
|
||||
logger.error("Error during RPC invocation", error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun obfuscate(error: Throwable): Throwable {
|
||||
val exposed = if (error.isWhitelisted()) error else InternalNodeException((error as? IdentifiableException)?.errorId)
|
||||
removeDetails(exposed)
|
||||
return exposed
|
||||
}
|
||||
|
||||
private fun removeDetails(error: Throwable) {
|
||||
error.stackTrace = arrayOf<StackTraceElement>()
|
||||
error.declaredField<Any?>("cause").value = null
|
||||
error.declaredField<Any?>("suppressedExceptions").value = null
|
||||
when (error) {
|
||||
is CordaException -> error.setCause(null)
|
||||
is CordaRuntimeException -> error.setCause(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Throwable.isWhitelisted(): Boolean {
|
||||
return whitelist.any { it.isInstance(this) }
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ErrorObfuscatingInvocationHandler(whitelist=$whitelist)"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ExceptionMaskingRpcOpsProxy"
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ import net.corda.testing.driver.internal.InProcessImpl
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
import net.corda.testing.driver.internal.OutOfProcessImpl
|
||||
import net.corda.testing.internal.setGlobalSerialization
|
||||
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
||||
import net.corda.testing.node.ClusterSpec
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
@ -1122,4 +1123,17 @@ private fun Config.toNodeOnly(): Config {
|
||||
}
|
||||
|
||||
internal fun DriverParameters.cordappsForAllNodes(): Set<TestCorDapp> = cordappsForAllNodes
|
||||
?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan)
|
||||
?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan)
|
||||
|
||||
fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture<NodeHandle> {
|
||||
var customOverrides = emptyMap<String, String>()
|
||||
if (!devMode) {
|
||||
val nodeDir = baseDirectory(providedName)
|
||||
val certificatesDirectory = nodeDir / "certificates"
|
||||
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
|
||||
val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
|
||||
p2pSslConfig.configureDevKeyAndTrustStores(providedName, signingCertStore, certificatesDirectory)
|
||||
customOverrides = mapOf("devMode" to "false")
|
||||
}
|
||||
return startNode(parameters, providedName = providedName, customOverrides = customOverrides)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user