diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 91a2c644df..230c486b46 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -4356,7 +4356,7 @@ public static final class net.corda.client.rpc.CordaRPCClientConfiguration$Compa public int getServerProtocolVersion() 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 (String) ## @net.corda.core.DoNotImplement public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index b95480b95b..f31c2d6a49 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -12,7 +12,6 @@ import net.corda.finance.DOLLARS import net.corda.finance.USD import net.corda.finance.contracts.getCashBalance import net.corda.finance.contracts.getCashBalances -import net.corda.finance.flows.CashException import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.schemas.CashSchemaV1 @@ -20,6 +19,7 @@ import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.nodeapi.exceptions.InternalNodeException import net.corda.testing.core.* import net.corda.testing.node.User import net.corda.testing.node.internal.NodeBasedTest @@ -101,7 +101,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C fun `sub-type of FlowException thrown by flow`() { login(rpcUser.username, rpcUser.password) val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity) - assertThatExceptionOfType(CashException::class.java).isThrownBy { + assertThatExceptionOfType(InternalNodeException::class.java).isThrownBy { handle.returnValue.getOrThrow() } } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt index 71596c6e5e..a11dcdeb11 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt @@ -2,9 +2,10 @@ package net.corda.client.rpc 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 * calling a method). */ -class PermissionException(msg: String) : CordaRuntimeException(msg) +class PermissionException(message: String) : CordaRuntimeException(message), RpcSerializableError diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index d9702f53aa..d2d15b87aa 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -5,6 +5,7 @@ package net.corda.core import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch +import net.corda.core.messaging.DataFeed import rx.Observable import rx.Observer @@ -44,3 +45,29 @@ fun Observable.toFuture(): CordaFuture = openFuture().also { } } } + +/** + * Returns a [DataFeed] that transforms errors according to the provided [transform] function. + */ +fun DataFeed.mapErrors(transform: (Throwable) -> Throwable): DataFeed { + + return copy(updates = updates.mapErrors(transform)) +} + +/** + * Returns a [DataFeed] that processes errors according to the provided [action]. + */ +fun DataFeed.doOnError(action: (Throwable) -> Unit): DataFeed { + + return copy(updates = updates.doOnError(action)) +} + +/** + * Returns an [Observable] that transforms errors according to the provided [transform] function. + */ +fun Observable.mapErrors(transform: (Throwable) -> Throwable): Observable { + + return onErrorResumeNext { error -> + Observable.error(transform(error)) + } +} diff --git a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt index 6ff8ce4f2c..c51deac096 100644 --- a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt @@ -39,6 +39,29 @@ fun CordaFuture.map(transform: (V) -> W): CordaFuture = CordaFu }) } +/** + * Returns a future that will also apply the passed closure on an error. + */ +fun CordaFuture.doOnError(accept: (Throwable) -> Unit): CordaFuture = CordaFutureImpl().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 CordaFuture.mapError(transform: (Throwable) -> Throwable): CordaFuture = CordaFutureImpl().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. * But if this future or the transform fails, the returned future's outcome is the same throwable. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index f4d0dcd113..ca7cbb2293 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,8 @@ from the previous milestone release. Unreleased ---------- +* Errors thrown by a Corda node will now reported to a calling RPC client with attention to serialization and obfuscation of internal data. + * 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 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/InternalNodeException.kt b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/InternalNodeException.kt new file mode 100644 index 0000000000..805841ee7c --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/InternalNodeException.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/OutdatedNetworkParameterHashException.kt b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/OutdatedNetworkParameterHashException.kt new file mode 100644 index 0000000000..8c21c2b943 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/OutdatedNetworkParameterHashException.kt @@ -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" + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/RejectedCommandException.kt b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/RejectedCommandException.kt index 8fd3004046..024535d274 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/RejectedCommandException.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/RejectedCommandException.kt @@ -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. */ -class RejectedCommandException(msg: String) : CordaRuntimeException(msg) \ No newline at end of file +class RejectedCommandException(message: String) : CordaRuntimeException(message), RpcSerializableError \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/RpcSerializableError.kt b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/RpcSerializableError.kt new file mode 100644 index 0000000000..8756f0f72f --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/RpcSerializableError.kt @@ -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 \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/adapters/InternalObfuscatingFlowHandle.kt b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/adapters/InternalObfuscatingFlowHandle.kt new file mode 100644 index 0000000000..fbef080c7a --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/adapters/InternalObfuscatingFlowHandle.kt @@ -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(val wrapped: FlowHandle) : FlowHandle by wrapped { + + override val returnValue = wrapped.returnValue.mapError(InternalNodeException.Companion::obfuscateIfInternal) +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/adapters/InternalObfuscatingFlowProgressHandle.kt b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/adapters/InternalObfuscatingFlowProgressHandle.kt new file mode 100644 index 0000000000..7b8f13f3cb --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/adapters/InternalObfuscatingFlowProgressHandle.kt @@ -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(val wrapped: FlowProgressHandle) : FlowProgressHandle 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) +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/ClientRelevantException.kt b/node/src/integration-test/kotlin/net/corda/ClientRelevantException.kt new file mode 100644 index 0000000000..614cbae99f --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/ClientRelevantException.kt @@ -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 \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 75d2418592..c1dc97f3f4 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -5,7 +5,6 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.flows.FlowLogic -import net.corda.core.flows.UnexpectedFlowEndException import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party 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.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl +import net.corda.nodeapi.exceptions.InternalNodeException import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME @@ -113,7 +113,7 @@ class AttachmentLoadingTests { driver { installIsolatedCordappTo(bankAName) val (bankA, bankB) = createTwoNodes() - assertFailsWith("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") { + assertFailsWith { bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcExceptionHandlingTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcExceptionHandlingTest.kt new file mode 100644 index 0000000000..ad3b08daaa --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcExceptionHandlingTest.kt @@ -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() { + + @Suspendable + override fun call(): String { + + throw GenericJDBCException("Something went wrong!", SQLException("Oops!")) + } +} + +@StartableByRPC +@InitiatingFlow +class InitFlow(private val party: Party) : FlowLogic() { + + @Suspendable + override fun call(): String { + + val session = initiateFlow(party) + return session.sendAndReceive("hey").unwrap { it } + } +} + +@InitiatedBy(InitFlow::class) +class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic() { + + @Suspendable + override fun call() { + + initiatingSession.receive().unwrap { it } + throw GenericJDBCException("Something went wrong!", SQLException("Oops!")) + } +} + +@StartableByRPC +class ClientRelevantErrorFlow(private val message: String) : FlowLogic() { + + @Suspendable + override fun call(): String { + + throw ClientRelevantException(message, SQLException("Oops!")) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index b1d3063e4f..93067458ed 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -203,14 +203,9 @@ internal class CordaRPCOpsImpl( } override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List { - try { - return database.transaction { - 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 + // TODO: this operation should not require an explicit transaction + return database.transaction { + services.attachments.queryAttachments(query, sorting) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 673d7bf73a..9242aba047 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -295,7 +295,11 @@ open class Node(configuration: NodeConfiguration, // Start up the MQ clients. rpcMessagingClient?.run { 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 { runOnStop += this::stop diff --git a/node/src/main/kotlin/net/corda/node/internal/RpcExceptionHandlingProxy.kt b/node/src/main/kotlin/net/corda/node/internal/RpcExceptionHandlingProxy.kt new file mode 100644 index 0000000000..a2e50114b3 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/RpcExceptionHandlingProxy.kt @@ -0,0 +1,147 @@ +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() + } + + override val protocolVersion: Int get() = delegate.protocolVersion + + override fun startFlowDynamic(logicType: Class>, vararg args: Any?): FlowHandle = wrap { + + val handle = delegate.startFlowDynamic(logicType, *args) + val result = InternalObfuscatingFlowHandle(handle) + result.returnValue.doOnError { error -> logger.error(error.message, error) } + result + } + + override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle = 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 vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class) = wrapFeed { delegate.vaultTrackBy(criteria, paging, sorting, contractStateType) } + + override fun vaultTrack(contractStateType: Class) = wrapFeed { delegate.vaultTrack(contractStateType) } + + override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria) = wrapFeed { delegate.vaultTrackByCriteria(contractStateType, criteria) } + + override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification) = wrapFeed { delegate.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) } + + override fun vaultTrackByWithSorting(contractStateType: Class, 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 vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class) = wrap { delegate.vaultQueryBy(criteria, paging, sorting, contractStateType) } + + override fun vaultQuery(contractStateType: Class) = wrap { delegate.vaultQuery(contractStateType) } + + override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class) = wrap { delegate.vaultQueryByCriteria(criteria, contractStateType) } + + override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification) = wrap { delegate.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) } + + override fun vaultQueryByWithSorting(contractStateType: Class, 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) + + private fun wrap(call: () -> RESULT): RESULT { + + return try { + call.invoke() + } catch (error: Throwable) { + logger.error(error.message, error) + throw InternalNodeException.obfuscateIfInternal(error) + } + } + + private fun wrapFeed(call: () -> DataFeed) = wrap { + + call.invoke().doOnError { error -> logger.error(error.message, error) }.mapErrors(InternalNodeException.Companion::obfuscateIfInternal) + } + + private fun wrapFuture(call: () -> CordaFuture): CordaFuture = wrap { call.invoke().mapError(InternalNodeException.Companion::obfuscateIfInternal).doOnError { error -> logger.error(error.message, error) } } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt index 2dc5d0b564..5bcc61f1e1 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt @@ -14,6 +14,7 @@ import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.NamedThreadFactory +import net.corda.nodeapi.exceptions.OutdatedNetworkParameterHashException import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME @@ -180,8 +181,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, .copyTo(baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME, StandardCopyOption.REPLACE_EXISTING) networkMapClient.ackNetworkParametersUpdate(sign(parametersHash)) } else { - throw IllegalArgumentException("Refused to accept parameters with hash $parametersHash because network map " + - "advertises update with hash $newParametersHash. Please check newest version") + throw throw OutdatedNetworkParameterHashException(parametersHash, newParametersHash) } } }