mirror of
https://github.com/corda/corda.git
synced 2025-02-01 16:58:27 +00:00
[CORDA-1264]: Ensure correct serialisation and masking for throwables raised by a node and propagated through RPC. (#2892)
This commit is contained in:
parent
196a24f030
commit
0d1d7daedc
@ -4390,7 +4390,7 @@ public static final class net.corda.client.rpc.CordaRPCClientConfiguration$Compa
|
|||||||
public int getServerProtocolVersion()
|
public int getServerProtocolVersion()
|
||||||
public void notifyServerAndClose()
|
public void notifyServerAndClose()
|
||||||
##
|
##
|
||||||
public final class net.corda.client.rpc.PermissionException extends net.corda.core.CordaRuntimeException
|
public final class net.corda.client.rpc.PermissionException extends net.corda.core.CordaRuntimeException implements net.corda.nodeapi.exceptions.RpcSerializableError
|
||||||
public <init>(String)
|
public <init>(String)
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable
|
@net.corda.core.DoNotImplement public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable
|
||||||
|
@ -12,13 +12,13 @@ import net.corda.finance.DOLLARS
|
|||||||
import net.corda.finance.USD
|
import net.corda.finance.USD
|
||||||
import net.corda.finance.contracts.getCashBalance
|
import net.corda.finance.contracts.getCashBalance
|
||||||
import net.corda.finance.contracts.getCashBalances
|
import net.corda.finance.contracts.getCashBalances
|
||||||
import net.corda.finance.flows.CashException
|
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
import net.corda.finance.flows.CashPaymentFlow
|
||||||
import net.corda.finance.schemas.CashSchemaV1
|
import net.corda.finance.schemas.CashSchemaV1
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
|
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import net.corda.testing.node.internal.NodeBasedTest
|
import net.corda.testing.node.internal.NodeBasedTest
|
||||||
@ -158,7 +158,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
fun `sub-type of FlowException thrown by flow`() {
|
fun `sub-type of FlowException thrown by flow`() {
|
||||||
login(rpcUser.username, rpcUser.password)
|
login(rpcUser.username, rpcUser.password)
|
||||||
val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity)
|
val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity)
|
||||||
assertThatExceptionOfType(CashException::class.java).isThrownBy {
|
assertThatExceptionOfType(InternalNodeException::class.java).isThrownBy {
|
||||||
handle.returnValue.getOrThrow()
|
handle.returnValue.getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package net.corda.client.rpc
|
package net.corda.client.rpc
|
||||||
|
|
||||||
import net.corda.core.CordaRuntimeException
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.nodeapi.exceptions.RpcSerializableError
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown to indicate that the calling user does not have permission for something they have requested (for example
|
* Thrown to indicate that the calling user does not have permission for something they have requested (for example
|
||||||
* calling a method).
|
* calling a method).
|
||||||
*/
|
*/
|
||||||
class PermissionException(msg: String) : CordaRuntimeException(msg)
|
class PermissionException(message: String) : CordaRuntimeException(message), RpcSerializableError
|
||||||
|
@ -5,6 +5,7 @@ package net.corda.core
|
|||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.concurrent.thenMatch
|
import net.corda.core.internal.concurrent.thenMatch
|
||||||
|
import net.corda.core.messaging.DataFeed
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Observer
|
import rx.Observer
|
||||||
|
|
||||||
@ -44,3 +45,29 @@ fun <T> Observable<T>.toFuture(): CordaFuture<T> = openFuture<T>().also {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [DataFeed] that transforms errors according to the provided [transform] function.
|
||||||
|
*/
|
||||||
|
fun <SNAPSHOT, ELEMENT> DataFeed<SNAPSHOT, ELEMENT>.mapErrors(transform: (Throwable) -> Throwable): DataFeed<SNAPSHOT, ELEMENT> {
|
||||||
|
|
||||||
|
return copy(updates = updates.mapErrors(transform))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [DataFeed] that processes errors according to the provided [action].
|
||||||
|
*/
|
||||||
|
fun <SNAPSHOT, ELEMENT> DataFeed<SNAPSHOT, ELEMENT>.doOnError(action: (Throwable) -> Unit): DataFeed<SNAPSHOT, ELEMENT> {
|
||||||
|
|
||||||
|
return copy(updates = updates.doOnError(action))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an [Observable] that transforms errors according to the provided [transform] function.
|
||||||
|
*/
|
||||||
|
fun <ELEMENT> Observable<ELEMENT>.mapErrors(transform: (Throwable) -> Throwable): Observable<ELEMENT> {
|
||||||
|
|
||||||
|
return onErrorResumeNext { error ->
|
||||||
|
Observable.error(transform(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -39,6 +39,29 @@ fun <V, W> CordaFuture<out V>.map(transform: (V) -> W): CordaFuture<W> = CordaFu
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a future that will also apply the passed closure on an error.
|
||||||
|
*/
|
||||||
|
fun <RESULT> CordaFuture<out RESULT>.doOnError(accept: (Throwable) -> Unit): CordaFuture<RESULT> = CordaFutureImpl<RESULT>().also { result ->
|
||||||
|
thenMatch({
|
||||||
|
result.capture { it }
|
||||||
|
}, {
|
||||||
|
accept(it)
|
||||||
|
result.setException(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a future that will map an error thrown using the provided [transform] function.
|
||||||
|
*/
|
||||||
|
fun <ELEMENT> CordaFuture<out ELEMENT>.mapError(transform: (Throwable) -> Throwable): CordaFuture<ELEMENT> = CordaFutureImpl<ELEMENT>().also { result ->
|
||||||
|
thenMatch({
|
||||||
|
result.capture { it }
|
||||||
|
}, {
|
||||||
|
result.setException(transform(it))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a future that will have the same outcome as the future returned by the given transform.
|
* Returns a future that will have the same outcome as the future returned by the given transform.
|
||||||
* But if this future or the transform fails, the returned future's outcome is the same throwable.
|
* But if this future or the transform fails, the returned future's outcome is the same throwable.
|
||||||
|
@ -7,6 +7,8 @@ Unreleased
|
|||||||
Here are brief summaries of what's changed between each snapshot release. This includes guidance on how to upgrade code
|
Here are brief summaries of what's changed between each snapshot release. This includes guidance on how to upgrade code
|
||||||
from the previous milestone release.
|
from the previous milestone release.
|
||||||
|
|
||||||
|
* Errors thrown by a Corda node will now reported to a calling RPC client with attention to serialization and obfuscation of internal data.
|
||||||
|
|
||||||
* Serializing an inner class (non-static nested class in Java, inner class in Kotlin) will be rejected explicitly by the serialization
|
* Serializing an inner class (non-static nested class in Java, inner class in Kotlin) will be rejected explicitly by the serialization
|
||||||
framework. Prior to this change it didn't work, but the error thrown was opaque (complaining about too few arguments
|
framework. Prior to this change it didn't work, but the error thrown was opaque (complaining about too few arguments
|
||||||
to a constructor). Whilst this was possible in the older Kryo implementation (Kryo passing null as the synthesised
|
to a constructor). Whilst this was possible in the older Kryo implementation (Kryo passing null as the synthesised
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package net.corda.nodeapi.exceptions
|
||||||
|
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
|
import java.io.InvalidClassException
|
||||||
|
|
||||||
|
// could change to use package name matching but trying to avoid reflection for now
|
||||||
|
private val whitelisted = setOf(
|
||||||
|
InvalidClassException::class,
|
||||||
|
RpcSerializableError::class
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [Exception] to signal RPC clients that something went wrong within a Corda node.
|
||||||
|
*/
|
||||||
|
class InternalNodeException(message: String) : CordaRuntimeException(message) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val DEFAULT_MESSAGE = "Something went wrong within the Corda node."
|
||||||
|
|
||||||
|
fun defaultMessage(): String = DEFAULT_MESSAGE
|
||||||
|
|
||||||
|
fun obfuscateIfInternal(wrapped: Throwable): Throwable {
|
||||||
|
|
||||||
|
(wrapped as? CordaRuntimeException)?.setCause(null)
|
||||||
|
return when {
|
||||||
|
whitelisted.any { it.isInstance(wrapped) } -> wrapped
|
||||||
|
else -> InternalNodeException(DEFAULT_MESSAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package net.corda.nodeapi.exceptions
|
||||||
|
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
|
||||||
|
class OutdatedNetworkParameterHashException(old: SecureHash, new: SecureHash) : CordaRuntimeException(TEMPLATE.format(old, new)), RpcSerializableError {
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val TEMPLATE = "Refused to accept parameters with hash %s because network map advertises update with hash %s. Please check newest version"
|
||||||
|
}
|
||||||
|
}
|
@ -5,4 +5,4 @@ import net.corda.core.CordaRuntimeException
|
|||||||
/**
|
/**
|
||||||
* Thrown to indicate that the command was rejected by the node, typically due to a special temporary mode.
|
* Thrown to indicate that the command was rejected by the node, typically due to a special temporary mode.
|
||||||
*/
|
*/
|
||||||
class RejectedCommandException(msg: String) : CordaRuntimeException(msg)
|
class RejectedCommandException(message: String) : CordaRuntimeException(message), RpcSerializableError
|
@ -0,0 +1,9 @@
|
|||||||
|
package net.corda.nodeapi.exceptions
|
||||||
|
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows an implementing [Throwable] to be propagated to RPC clients.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
interface RpcSerializableError
|
@ -0,0 +1,15 @@
|
|||||||
|
package net.corda.nodeapi.exceptions.adapters
|
||||||
|
|
||||||
|
import net.corda.core.internal.concurrent.mapError
|
||||||
|
import net.corda.core.messaging.FlowHandle
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter able to mask errors within a Corda node for RPC clients.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
data class InternalObfuscatingFlowHandle<RESULT>(val wrapped: FlowHandle<RESULT>) : FlowHandle<RESULT> by wrapped {
|
||||||
|
|
||||||
|
override val returnValue = wrapped.returnValue.mapError(InternalNodeException.Companion::obfuscateIfInternal)
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.corda.nodeapi.exceptions.adapters
|
||||||
|
|
||||||
|
import net.corda.core.internal.concurrent.mapError
|
||||||
|
import net.corda.core.mapErrors
|
||||||
|
import net.corda.core.messaging.FlowProgressHandle
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter able to mask errors within a Corda node for RPC clients.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
class InternalObfuscatingFlowProgressHandle<RESULT>(val wrapped: FlowProgressHandle<RESULT>) : FlowProgressHandle<RESULT> by wrapped {
|
||||||
|
|
||||||
|
override val returnValue = wrapped.returnValue.mapError(InternalNodeException.Companion::obfuscateIfInternal)
|
||||||
|
|
||||||
|
override val progress = wrapped.progress.mapErrors(InternalNodeException.Companion::obfuscateIfInternal)
|
||||||
|
|
||||||
|
override val stepsTreeIndexFeed = wrapped.stepsTreeIndexFeed?.mapErrors(InternalNodeException.Companion::obfuscateIfInternal)
|
||||||
|
|
||||||
|
override val stepsTreeFeed = wrapped.stepsTreeFeed?.mapErrors(InternalNodeException.Companion::obfuscateIfInternal)
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package net.corda
|
||||||
|
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
|
import net.corda.nodeapi.exceptions.RpcSerializableError
|
||||||
|
|
||||||
|
class ClientRelevantException(message: String?, cause: Throwable?) : CordaRuntimeException(message, cause), RpcSerializableError
|
@ -5,7 +5,6 @@ import com.nhaarman.mockito_kotlin.whenever
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.UnexpectedFlowEndException
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
@ -22,6 +21,7 @@ import net.corda.core.utilities.contextLogger
|
|||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||||
|
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||||
@ -114,7 +114,7 @@ class AttachmentLoadingTests {
|
|||||||
driver {
|
driver {
|
||||||
installIsolatedCordappTo(bankAName)
|
installIsolatedCordappTo(bankAName)
|
||||||
val (bankA, bankB) = createTwoNodes()
|
val (bankA, bankB) = createTwoNodes()
|
||||||
assertFailsWith<UnexpectedFlowEndException>("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") {
|
assertFailsWith<InternalNodeException> {
|
||||||
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
|
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
package net.corda.node.services.rpc
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.ClientRelevantException
|
||||||
|
import net.corda.core.flows.*
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
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.singleIdentity
|
||||||
|
import net.corda.testing.driver.DriverParameters
|
||||||
|
import net.corda.testing.driver.NodeParameters
|
||||||
|
import net.corda.testing.driver.driver
|
||||||
|
import net.corda.testing.node.User
|
||||||
|
import org.assertj.core.api.Assertions.assertThatCode
|
||||||
|
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
|
||||||
|
import org.hibernate.exception.GenericJDBCException
|
||||||
|
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)) {
|
||||||
|
|
||||||
|
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
|
|
||||||
|
assertThatCode { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||||
|
|
||||||
|
assertThat(exception).hasNoCause()
|
||||||
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
|
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `rpc client handles client-relevant exceptions thrown on node side`() {
|
||||||
|
|
||||||
|
driver(DriverParameters(startNodesInProcess = true)) {
|
||||||
|
|
||||||
|
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
|
val clientRelevantMessage = "This is for the players!"
|
||||||
|
|
||||||
|
assertThatCode { node.rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow() }.isInstanceOfSatisfying(ClientRelevantException::class.java) { exception ->
|
||||||
|
|
||||||
|
assertThat(exception).hasNoCause()
|
||||||
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
|
assertThat(exception.message).isEqualTo(clientRelevantMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `rpc client handles exceptions thrown on counter-party side`() {
|
||||||
|
|
||||||
|
driver(DriverParameters(startNodesInProcess = true)) {
|
||||||
|
|
||||||
|
val nodeA = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
|
val nodeB = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
|
|
||||||
|
assertThatCode { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||||
|
|
||||||
|
assertThat(exception).hasNoCause()
|
||||||
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
|
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
class Flow : FlowLogic<String>() {
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): String {
|
||||||
|
|
||||||
|
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
@InitiatingFlow
|
||||||
|
class InitFlow(private val party: Party) : FlowLogic<String>() {
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): String {
|
||||||
|
|
||||||
|
val session = initiateFlow(party)
|
||||||
|
return session.sendAndReceive<String>("hey").unwrap { it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitiatedBy(InitFlow::class)
|
||||||
|
class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call() {
|
||||||
|
|
||||||
|
initiatingSession.receive<String>().unwrap { it }
|
||||||
|
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
class ClientRelevantErrorFlow(private val message: String) : FlowLogic<String>() {
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): String {
|
||||||
|
|
||||||
|
throw ClientRelevantException(message, SQLException("Oops!"))
|
||||||
|
}
|
||||||
|
}
|
@ -204,15 +204,10 @@ internal class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
|
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
|
||||||
try {
|
// TODO: this operation should not require an explicit transaction
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
services.attachments.queryAttachments(query, sorting)
|
services.attachments.queryAttachments(query, sorting)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
// log and rethrow exception so we keep a copy server side
|
|
||||||
log.error(e.message)
|
|
||||||
throw e.cause ?: e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun currentNodeTime(): Instant = Instant.now(services.clock)
|
override fun currentNodeTime(): Instant = Instant.now(services.clock)
|
||||||
|
@ -27,8 +27,11 @@ import net.corda.node.internal.security.RPCSecurityManagerImpl
|
|||||||
import net.corda.node.serialization.KryoServerSerializationScheme
|
import net.corda.node.serialization.KryoServerSerializationScheme
|
||||||
import net.corda.node.services.api.NodePropertiesStore
|
import net.corda.node.services.api.NodePropertiesStore
|
||||||
import net.corda.node.services.api.SchemaService
|
import net.corda.node.services.api.SchemaService
|
||||||
import net.corda.node.services.config.*
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
|
import net.corda.node.services.config.SecurityConfiguration
|
||||||
|
import net.corda.node.services.config.VerifierType
|
||||||
import net.corda.node.services.config.shell.shellUser
|
import net.corda.node.services.config.shell.shellUser
|
||||||
|
import net.corda.node.services.config.shouldInitCrashShell
|
||||||
import net.corda.node.services.messaging.*
|
import net.corda.node.services.messaging.*
|
||||||
import net.corda.node.services.rpc.ArtemisRpcBroker
|
import net.corda.node.services.rpc.ArtemisRpcBroker
|
||||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||||
@ -281,7 +284,11 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
// Start up the MQ clients.
|
// Start up the MQ clients.
|
||||||
rpcMessagingClient?.run {
|
rpcMessagingClient?.run {
|
||||||
runOnStop += this::close
|
runOnStop += this::close
|
||||||
start(rpcOps, securityManager)
|
when (rpcOps) {
|
||||||
|
// not sure what this RPCOps base interface is for
|
||||||
|
is SecureCordaRPCOps -> start(RpcExceptionHandlingProxy(rpcOps), securityManager)
|
||||||
|
else -> start(rpcOps, securityManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
verifierMessagingClient?.run {
|
verifierMessagingClient?.run {
|
||||||
runOnStop += this::stop
|
runOnStop += this::stop
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
package net.corda.node.internal
|
||||||
|
|
||||||
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.doOnError
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.concurrent.doOnError
|
||||||
|
import net.corda.core.internal.concurrent.mapError
|
||||||
|
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.FlowProgressHandle
|
||||||
|
import net.corda.core.node.services.vault.*
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||||
|
import net.corda.nodeapi.exceptions.adapters.InternalObfuscatingFlowHandle
|
||||||
|
import net.corda.nodeapi.exceptions.adapters.InternalObfuscatingFlowProgressHandle
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
class RpcExceptionHandlingProxy(private val delegate: SecureCordaRPCOps) : CordaRPCOps {
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private val logger = loggerFor<RpcExceptionHandlingProxy>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val protocolVersion: Int get() = delegate.protocolVersion
|
||||||
|
|
||||||
|
override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> = wrap {
|
||||||
|
|
||||||
|
val handle = delegate.startFlowDynamic(logicType, *args)
|
||||||
|
val result = InternalObfuscatingFlowHandle(handle)
|
||||||
|
result.returnValue.doOnError { error -> logger.error(error.message, error) }
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> = wrap {
|
||||||
|
|
||||||
|
val handle = delegate.startTrackedFlowDynamic(logicType, *args)
|
||||||
|
val result = InternalObfuscatingFlowProgressHandle(handle)
|
||||||
|
result.returnValue.doOnError { error -> logger.error(error.message, error) }
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun waitUntilNetworkReady() = wrapFuture(delegate::waitUntilNetworkReady)
|
||||||
|
|
||||||
|
override fun stateMachinesFeed() = wrapFeed(delegate::stateMachinesFeed)
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>) = wrapFeed { delegate.vaultTrackBy(criteria, paging, sorting, contractStateType) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrack(contractStateType: Class<out T>) = wrapFeed { delegate.vaultTrack(contractStateType) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria) = wrapFeed { delegate.vaultTrackByCriteria(contractStateType, criteria) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification) = wrapFeed { delegate.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort) = wrapFeed { delegate.vaultTrackByWithSorting(contractStateType, criteria, sorting) }
|
||||||
|
|
||||||
|
override fun stateMachineRecordedTransactionMappingFeed() = wrapFeed(delegate::stateMachineRecordedTransactionMappingFeed)
|
||||||
|
|
||||||
|
override fun networkMapFeed() = wrapFeed(delegate::networkMapFeed)
|
||||||
|
|
||||||
|
override fun networkParametersFeed() = wrapFeed(delegate::networkParametersFeed)
|
||||||
|
|
||||||
|
override fun internalVerifiedTransactionsFeed() = wrapFeed(delegate::internalVerifiedTransactionsFeed)
|
||||||
|
|
||||||
|
override fun stateMachinesSnapshot() = wrap(delegate::stateMachinesSnapshot)
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>) = wrap { delegate.vaultQueryBy(criteria, paging, sorting, contractStateType) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>) = wrap { delegate.vaultQuery(contractStateType) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>) = wrap { delegate.vaultQueryByCriteria(criteria, contractStateType) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification) = wrap { delegate.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) }
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort) = wrap { delegate.vaultQueryByWithSorting(contractStateType, criteria, sorting) }
|
||||||
|
|
||||||
|
override fun internalVerifiedTransactionsSnapshot() = wrap(delegate::internalVerifiedTransactionsSnapshot)
|
||||||
|
|
||||||
|
override fun stateMachineRecordedTransactionMappingSnapshot() = wrap(delegate::stateMachineRecordedTransactionMappingSnapshot)
|
||||||
|
|
||||||
|
override fun networkMapSnapshot() = wrap(delegate::networkMapSnapshot)
|
||||||
|
|
||||||
|
override fun acceptNewNetworkParameters(parametersHash: SecureHash) = wrap { delegate.acceptNewNetworkParameters(parametersHash) }
|
||||||
|
|
||||||
|
override fun nodeInfo() = wrap(delegate::nodeInfo)
|
||||||
|
|
||||||
|
override fun notaryIdentities() = wrap(delegate::notaryIdentities)
|
||||||
|
|
||||||
|
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = wrap { delegate.addVaultTransactionNote(txnId, txnNote) }
|
||||||
|
|
||||||
|
override fun getVaultTransactionNotes(txnId: SecureHash) = wrap { delegate.getVaultTransactionNotes(txnId) }
|
||||||
|
|
||||||
|
override fun attachmentExists(id: SecureHash) = wrap { delegate.attachmentExists(id) }
|
||||||
|
|
||||||
|
override fun openAttachment(id: SecureHash) = wrap { delegate.openAttachment(id) }
|
||||||
|
|
||||||
|
override fun uploadAttachment(jar: InputStream) = wrap { delegate.uploadAttachment(jar) }
|
||||||
|
|
||||||
|
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String) = wrap { delegate.uploadAttachmentWithMetadata(jar, uploader, filename) }
|
||||||
|
|
||||||
|
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?) = wrap { delegate.queryAttachments(query, sorting) }
|
||||||
|
|
||||||
|
override fun currentNodeTime() = wrap(delegate::currentNodeTime)
|
||||||
|
|
||||||
|
override fun wellKnownPartyFromAnonymous(party: AbstractParty) = wrap { delegate.wellKnownPartyFromAnonymous(party) }
|
||||||
|
|
||||||
|
override fun partyFromKey(key: PublicKey) = wrap { delegate.partyFromKey(key) }
|
||||||
|
|
||||||
|
override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name) = wrap { delegate.wellKnownPartyFromX500Name(x500Name) }
|
||||||
|
|
||||||
|
override fun notaryPartyFromX500Name(x500Name: CordaX500Name) = wrap { delegate.notaryPartyFromX500Name(x500Name) }
|
||||||
|
|
||||||
|
override fun partiesFromName(query: String, exactMatch: Boolean) = wrap { delegate.partiesFromName(query, exactMatch) }
|
||||||
|
|
||||||
|
override fun registeredFlows() = wrap(delegate::registeredFlows)
|
||||||
|
|
||||||
|
override fun nodeInfoFromParty(party: AbstractParty) = wrap { delegate.nodeInfoFromParty(party) }
|
||||||
|
|
||||||
|
override fun clearNetworkMapCache() = wrap(delegate::clearNetworkMapCache)
|
||||||
|
|
||||||
|
override fun setFlowsDrainingModeEnabled(enabled: Boolean) = wrap { delegate.setFlowsDrainingModeEnabled(enabled) }
|
||||||
|
|
||||||
|
override fun isFlowsDrainingModeEnabled() = wrap(delegate::isFlowsDrainingModeEnabled)
|
||||||
|
|
||||||
|
override fun shutdown() = wrap(delegate::shutdown)
|
||||||
|
|
||||||
|
private fun <RESULT> wrap(call: () -> RESULT): RESULT {
|
||||||
|
|
||||||
|
return try {
|
||||||
|
call.invoke()
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
logger.error(error.message, error)
|
||||||
|
throw InternalNodeException.obfuscateIfInternal(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <SNAPSHOT, ELEMENT> wrapFeed(call: () -> DataFeed<SNAPSHOT, ELEMENT>) = wrap {
|
||||||
|
|
||||||
|
call.invoke().doOnError { error -> logger.error(error.message, error) }.mapErrors(InternalNodeException.Companion::obfuscateIfInternal)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <RESULT> wrapFuture(call: () -> CordaFuture<RESULT>): CordaFuture<RESULT> = wrap { call.invoke().mapError(InternalNodeException.Companion::obfuscateIfInternal).doOnError { error -> logger.error(error.message, error) } }
|
||||||
|
}
|
@ -14,6 +14,7 @@ import net.corda.core.utilities.contextLogger
|
|||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.utilities.NamedThreadFactory
|
import net.corda.node.utilities.NamedThreadFactory
|
||||||
|
import net.corda.nodeapi.exceptions.OutdatedNetworkParameterHashException
|
||||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
|
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||||
@ -183,8 +184,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
networkMapClient.ackNetworkParametersUpdate(sign(parametersHash))
|
networkMapClient.ackNetworkParametersUpdate(sign(parametersHash))
|
||||||
logger.info("Accepted network parameter update $update: $newNetParams")
|
logger.info("Accepted network parameter update $update: $newNetParams")
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Refused to accept parameters with hash $parametersHash because network map " +
|
throw OutdatedNetworkParameterHashException(parametersHash, newParametersHash)
|
||||||
"advertises update with hash $newParametersHash. Please check newest version")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user