CORDA-3959: Eliminate InternalCordaRPCOps (#6600)

* CORDA-3959: Make `ThreadContextAdjustingRpcOpsProxy` flexible for `RPCOps` it uses

* CORDA-3959: More changes towards supporting multiple `RPCOps` implementations

* CORDA-3959: Detekt baseline update

* CORDA-3959: Integration test compilation fix

* CORDA-3959: Introduce `CheckpointRPCOpsImpl` and wire it on

* CORDA-3959: Use multiple RPCOps interfaces in the shell commands

* CORDA-3959: Detekt baseline update

* CORDA-3959: Update RPCPermissionsTests

* CORDA-3959: Update RPCSecurityManagerTest

* CORDA-3959: Remove deprecated marker and rename the property

* CORDA-3959: Detekt baseline

* CORDA-3959: Introduce AttachmentTrustInfoRPCOpsImpl and wire it on

* CORDA-3959: Delete `InternalCordaRPCOps`

* CORDA-3959: Detekt baseline update

* CORDA-3959: Rename `CheckpointRPCOps` to `FlowManagerRPCOps`
This commit is contained in:
Viktor Kolomeyko 2020-08-13 14:41:52 +01:00 committed by GitHub
parent c12c582eb4
commit b81eb1559d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 643 additions and 318 deletions

View File

@ -12,7 +12,6 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.createInstancesOfClassesImplementing
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.SerializationCustomSerializer
@ -494,7 +493,7 @@ class CordaRPCClient private constructor(
}
}
private fun getRpcClient(): RPCClient<InternalCordaRPCOps> {
private fun getRpcClient(): RPCClient<CordaRPCOps> {
return when {
// Client->RPC broker
haAddressPool.isEmpty() -> RPCClient(
@ -619,7 +618,7 @@ class CordaRPCClient private constructor(
)
} else {
CordaRPCConnection(getRpcClient().start(
InternalCordaRPCOps::class.java,
CordaRPCOps::class.java,
username,
password,
externalTrace,

View File

@ -19,7 +19,6 @@ import net.corda.core.internal.LazyStickyPool
import net.corda.core.internal.LifeCycle
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.times
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
@ -363,10 +362,10 @@ internal class RPCClientProxyHandler(
private fun produceMethodFullyQualifiedName(method: Method) : String {
/*
* Until version 4.3, rpc calls did not include class names.
* Up to this version, only CordaRPCOps and InternalCordaRPCOps were supported.
* Up to this version, only CordaRPCOps was supported.
* So, for these classes only methods are sent across the wire to preserve backwards compatibility.
*/
return if (CordaRPCOps::class.java == rpcOpsClass || InternalCordaRPCOps::class.java == rpcOpsClass) {
return if (CordaRPCOps::class.java == rpcOpsClass) {
method.name
} else {
rpcOpsClass.name + CLASS_METHOD_DIVIDER + method.name

View File

@ -19,7 +19,6 @@ import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConn
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConnection.CurrentState.UNCONNECTED
import net.corda.client.rpc.reconnect.CouldNotStartFlowException
import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.min
import net.corda.core.internal.times
import net.corda.core.internal.uncheckedCast
@ -65,7 +64,7 @@ import java.util.concurrent.TimeUnit
// ReconnectingObservables and other things can attach themselves as listeners for reconnect events.
class ReconnectingCordaRPCOps private constructor(
val reconnectingRPCConnection: ReconnectingRPCConnection
) : InternalCordaRPCOps by proxy(reconnectingRPCConnection) {
) : CordaRPCOps by proxy(reconnectingRPCConnection) {
constructor(
nodeHostAndPorts: List<NetworkHostAndPort>,
username: String,
@ -86,11 +85,11 @@ class ReconnectingCordaRPCOps private constructor(
observersPool))
private companion object {
private val log = contextLogger()
private fun proxy(reconnectingRPCConnection: ReconnectingRPCConnection): InternalCordaRPCOps {
private fun proxy(reconnectingRPCConnection: ReconnectingRPCConnection): CordaRPCOps {
return Proxy.newProxyInstance(
this::class.java.classLoader,
arrayOf(InternalCordaRPCOps::class.java),
ErrorInterceptingHandler(reconnectingRPCConnection)) as InternalCordaRPCOps
arrayOf(CordaRPCOps::class.java),
ErrorInterceptingHandler(reconnectingRPCConnection)) as CordaRPCOps
}
}
private val retryFlowsPool = Executors.newScheduledThreadPool(1)

View File

@ -1,6 +1,8 @@
package net.corda.client.rpc
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName
import net.corda.node.services.rpc.rpcContext
import net.corda.testing.node.User
import net.corda.testing.node.internal.RPCDriverDSL
@ -25,16 +27,17 @@ class RPCPermissionsTests : AbstractRPCTest() {
fun validatePermission(method: String, target: String? = null)
}
class TestOpsImpl : TestOps {
private class TestOpsImpl : TestOps {
override val protocolVersion = 1000
override fun validatePermission(method: String, target: String?) {
val methodFullName = methodFullName(CordaRPCOps::class.java, method)
val authorized = if (target == null) {
rpcContext().isPermitted(method)
rpcContext().isPermitted(methodFullName)
} else {
rpcContext().isPermitted(method, target)
rpcContext().isPermitted(methodFullName, target)
}
if (!authorized) {
throw PermissionException("RPC user not authorized")
throw PermissionException("RPC user not authorized for: $method")
}
}
}

View File

@ -86,7 +86,7 @@ class ContractUpgradeFlowRPCTest : WithContracts, WithFinality {
return startRpcClient<CordaRPCOps>(
rpcAddress = startRpcServer(
rpcUser = user,
ops = node.rpcOps
ops = node.cordaRPCOps
).get().broker.hostAndPort!!,
username = user.username,
password = user.password

View File

@ -0,0 +1,11 @@
package net.corda.core.internal.messaging
import net.corda.core.internal.AttachmentTrustInfo
import net.corda.core.messaging.RPCOps
interface AttachmentTrustInfoRPCOps : RPCOps {
/**
* Get all the attachment trust information
*/
val attachmentTrustInfos: List<AttachmentTrustInfo>
}

View File

@ -0,0 +1,13 @@
package net.corda.core.internal.messaging
import net.corda.core.messaging.RPCOps
/**
* RPC operations to perform operations related to flows including management of associated persistent states like checkpoints.
*/
interface FlowManagerRPCOps : RPCOps {
/**
* Dump all the current flow checkpoints as JSON into a zip file in the node's log directory.
*/
fun dumpCheckpoints()
}

View File

@ -1,16 +0,0 @@
package net.corda.core.internal.messaging
import net.corda.core.internal.AttachmentTrustInfo
import net.corda.core.messaging.CordaRPCOps
/**
* Contains internal RPC functions that should not be publicly exposed in [CordaRPCOps]
*/
interface InternalCordaRPCOps : CordaRPCOps {
/** Dump all the current flow checkpoints as JSON into a zip file in the node's log directory. */
fun dumpCheckpoints()
/** Get all attachment trust information */
val attachmentTrustInfos: List<AttachmentTrustInfo>
}

View File

@ -151,7 +151,7 @@
<ID>ComplexMethod:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$ private fun pollServerForCertificates(requestId: String): List&lt;X509Certificate&gt;</ID>
<ID>ComplexMethod:NewTransaction.kt$NewTransaction$fun show(window: Window)</ID>
<ID>ComplexMethod:NewTransaction.kt$NewTransaction$private fun newTransactionDialog(window: Window)</ID>
<ID>ComplexMethod:Node.kt$Node$override fun startMessagingService(rpcOps: RPCOps, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters)</ID>
<ID>ComplexMethod:Node.kt$Node$override fun startMessagingService(rpcOps: List&lt;RPCOps&gt;, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters)</ID>
<ID>ComplexMethod:NodeNamedCache.kt$DefaultNamedCacheFactory$open protected fun &lt;K, V&gt; configuredForNamed(caffeine: Caffeine&lt;K, V&gt;, name: String): Caffeine&lt;K, V&gt;</ID>
<ID>ComplexMethod:NodeVaultService.kt$NodeVaultService$@Throws(VaultQueryException::class) private fun &lt;T : ContractState&gt; _queryBy(criteria: QueryCriteria, paging_: PageSpecification, sorting: Sort, contractStateType: Class&lt;out T&gt;, skipPagingChecks: Boolean): Vault.Page&lt;T&gt;</ID>
<ID>ComplexMethod:NodeVaultService.kt$NodeVaultService$private fun makeUpdates(batch: Iterable&lt;CoreTransaction&gt;, statesToRecord: StatesToRecord, previouslySeen: Boolean): List&lt;Vault.Update&lt;ContractState&gt;&gt;</ID>
@ -159,15 +159,12 @@
<ID>ComplexMethod:ObjectDiffer.kt$ObjectDiffer$fun diff(a: Any?, b: Any?): DiffTree?</ID>
<ID>ComplexMethod:Obligation.kt$Obligation$override fun verify(tx: LedgerTransaction)</ID>
<ID>ComplexMethod:QuasarInstrumentationHook.kt$QuasarInstrumentationHookAgent.Companion$@JvmStatic fun premain(argumentsString: String?, instrumentation: Instrumentation)</ID>
<ID>ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$ private fun close(notify: Boolean = true)</ID>
<ID>ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$// The handler for Artemis messages. private fun artemisMessageHandler(message: ClientMessage)</ID>
<ID>ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$// This is the general function that transforms a client side RPC to internal Artemis messages. override fun invoke(proxy: Any, method: Method, arguments: Array&lt;out Any?&gt;?): Any?</ID>
<ID>ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$private fun attemptReconnect()</ID>
<ID>ComplexMethod:RPCServer.kt$RPCServer$private fun clientArtemisMessageHandler(artemisMessage: ClientMessage)</ID>
<ID>ComplexMethod:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ReconnectingRPCConnection$ private tailrec fun establishConnectionWithRetry( retryInterval: Duration, roundRobinIndex: Int = 0, retries: Int = -1 ): CordaRPCConnection?</ID>
<ID>ComplexMethod:RemoteTypeCarpenter.kt$SchemaBuildingRemoteTypeCarpenter$override fun carpent(typeInformation: RemoteTypeInformation): Type</ID>
<ID>ComplexMethod:SchemaMigration.kt$SchemaMigration$ private fun migrateOlderDatabaseToUseLiquibase(existingCheckpoints: Boolean): Boolean</ID>
<ID>ComplexMethod:SchemaMigration.kt$SchemaMigration$private fun doRunMigration( run: Boolean, check: Boolean, existingCheckpoints: Boolean? = null )</ID>
<ID>ComplexMethod:SendTransactionFlow.kt$DataVendingFlow$@Suspendable override fun call(): Void?</ID>
<ID>ComplexMethod:ShellCmdLineOptions.kt$ShellCmdLineOptions$private fun toConfigFile(): Config</ID>
<ID>ComplexMethod:StartedFlowTransition.kt$StartedFlowTransition$override fun transition(): TransitionResult</ID>
@ -176,7 +173,6 @@
<ID>ComplexMethod:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest$@Test(timeout=300_000) fun testClientServerTlsExchange()</ID>
<ID>ComplexMethod:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$@Test(timeout=300_000) fun testClientServerTlsExchange()</ID>
<ID>ComplexMethod:TransactionUtils.kt$ fun createComponentGroups(inputs: List&lt;StateRef&gt;, outputs: List&lt;TransactionState&lt;ContractState&gt;&gt;, commands: List&lt;Command&lt;*&gt;&gt;, attachments: List&lt;SecureHash&gt;, notary: Party?, timeWindow: TimeWindow?, references: List&lt;StateRef&gt;, networkParametersHash: SecureHash?): List&lt;ComponentGroup&gt;</ID>
<ID>ComplexMethod:TransitionExecutorImpl.kt$TransitionExecutorImpl$@Suppress("NestedBlockDepth", "ReturnCount") @Suspendable override fun executeTransition( fiber: FlowFiber, previousState: StateMachineState, event: Event, transition: TransitionResult, actionExecutor: ActionExecutor ): Pair&lt;FlowContinuation, StateMachineState&gt;</ID>
<ID>ComplexMethod:TypeModellingFingerPrinter.kt$FingerPrintingState$// For a type we haven't seen before, determine the correct path depending on the type of type it is. private fun fingerprintNewType(type: LocalTypeInformation)</ID>
<ID>ComplexMethod:UniversalContract.kt$UniversalContract$override fun verify(tx: LedgerTransaction)</ID>
<ID>ComplexMethod:Util.kt$fun &lt;T&gt; debugCompare(perLeft: Perceivable&lt;T&gt;, perRight: Perceivable&lt;T&gt;)</ID>
@ -612,8 +608,6 @@
<ID>LongParameterList:AbstractCashSelection.kt$AbstractCashSelection$(connection: Connection, amount: Amount&lt;Currency&gt;, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set&lt;AbstractParty&gt;, withIssuerRefs: Set&lt;OpaqueBytes&gt;, withResultSet: (ResultSet) -&gt; Boolean)</ID>
<ID>LongParameterList:AbstractCashSelection.kt$AbstractCashSelection$(services: ServiceHub, amount: Amount&lt;Currency&gt;, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set&lt;AbstractParty&gt;, withIssuerRefs: Set&lt;OpaqueBytes&gt;, stateAndRefs: MutableList&lt;StateAndRef&lt;Cash.State&gt;&gt;)</ID>
<ID>LongParameterList:AbstractCashSelection.kt$AbstractCashSelection$(services: ServiceHub, amount: Amount&lt;Currency&gt;, onlyFromIssuerParties: Set&lt;AbstractParty&gt; = emptySet(), notary: Party? = null, lockId: UUID, withIssuerRefs: Set&lt;OpaqueBytes&gt; = emptySet())</ID>
<ID>LongParameterList:AbstractNode.kt$(databaseConfig: DatabaseConfig, wellKnownPartyFromX500Name: (CordaX500Name) -&gt; Party?, wellKnownPartyFromAnonymous: (AbstractParty) -&gt; Party?, schemaService: SchemaService, hikariProperties: Properties, cacheFactory: NamedCacheFactory, customClassLoader: ClassLoader?)</ID>
<ID>LongParameterList:AbstractNode.kt$(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set&lt;MappedSchema&gt;, metricRegistry: MetricRegistry? = null, cordappLoader: CordappLoader? = null, currentDir: Path? = null, ourName: CordaX500Name)</ID>
<ID>LongParameterList:ArtemisMessagingServer.kt$ArtemisMessagingServer$(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false, deleteDurableQueue: Boolean = false, createNonDurableQueue: Boolean = false, deleteNonDurableQueue: Boolean = false, manage: Boolean = false, browse: Boolean = false)</ID>
<ID>LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, journalBufferTimeout: Int?, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean)</ID>
<ID>LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, journalBufferTimeout: Int?, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean)</ID>
@ -655,13 +649,11 @@
<ID>LongParameterList:IdenticonRenderer.kt$IdenticonRenderer$(g: GraphicsContext, x: Double, y: Double, patchIndex: Int, turn: Int, patchSize: Double, _invert: Boolean, color: PatchColor)</ID>
<ID>LongParameterList:Injectors.kt$( metricRegistry: MetricRegistry, parallelism: Int, overallDuration: Duration, injectionRate: Rate, queueSizeMetricName: String = "QueueSize", workDurationMetricName: String = "WorkDuration", work: () -&gt; Unit )</ID>
<ID>LongParameterList:InteractiveShell.kt$InteractiveShell$(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer, inputObjectMapper: ObjectMapper = createYamlInputMapper(rpcOps))</ID>
<ID>LongParameterList:InternalTestUtils.kt$(hikariProperties: Properties, databaseConfig: DatabaseConfig, wellKnownPartyFromX500Name: (CordaX500Name) -&gt; Party?, wellKnownPartyFromAnonymous: (AbstractParty) -&gt; Party?, schemaService: SchemaService = NodeSchemaService(), internalSchemas: Set&lt;MappedSchema&gt; = NodeSchemaService().internalSchemas(), cacheFactory: NamedCacheFactory = TestingNamedCacheFactory(), ourName: CordaX500Name = TestIdentity(ALICE_NAME, 70).name)</ID>
<ID>LongParameterList:InternalTestUtils.kt$(inputs: List&lt;StateRef&gt;, attachments: List&lt;SecureHash&gt;, outputs: List&lt;TransactionState&lt;*&gt;&gt;, commands: List&lt;Command&lt;*&gt;&gt;, notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt = PrivacySalt())</ID>
<ID>LongParameterList:JarSignatureTestUtils.kt$JarSignatureTestUtils$(alias: String = "Test", storePassword: String = "secret!", name: String = CODE_SIGNER.toString(), keyalg: String = "RSA", keyPassword: String = storePassword, storeName: String = "_teststore")</ID>
<ID>LongParameterList:MockServices.kt$MockServices.Companion$( cordappLoader: CordappLoader, identityService: IdentityService, networkParameters: NetworkParameters, initialIdentity: TestIdentity, moreKeys: Set&lt;KeyPair&gt;, keyManagementService: KeyManagementService, schemaService: SchemaService, persistence: CordaPersistence )</ID>
<ID>LongParameterList:MockServices.kt$MockServices.Companion$( cordappPackages: List&lt;String&gt;, initialIdentity: TestIdentity, networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN), moreKeys: Set&lt;KeyPair&gt;, moreIdentities: Set&lt;PartyAndCertificate&gt;, cacheFactory: TestingNamedCacheFactory = TestingNamedCacheFactory() )</ID>
<ID>LongParameterList:NetworkBootstrapperTest.kt$NetworkBootstrapperTest$(copyCordapps: CopyCordapps = CopyCordapps.FirstRunOnly, packageOwnership: Map&lt;String, PublicKey&gt;? = emptyMap(), minimumPlatformVerison: Int? = PLATFORM_VERSION, maxMessageSize: Int? = DEFAULT_MAX_MESSAGE_SIZE, maxTransactionSize: Int? = DEFAULT_MAX_TRANSACTION_SIZE, eventHorizon: Duration? = 30.days)</ID>
<ID>LongParameterList:NetworkMapUpdater.kt$NetworkMapUpdater$(trustRoot: X509Certificate, currentParametersHash: SecureHash, ourNodeInfo: SignedNodeInfo, networkParameters: NetworkParameters, keyManagementService: KeyManagementService, networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings)</ID>
<ID>LongParameterList:NetworkParameters.kt$NetworkParameters$(minimumPlatformVersion: Int = this.minimumPlatformVersion, notaries: List&lt;NotaryInfo&gt; = this.notaries, maxMessageSize: Int = this.maxMessageSize, maxTransactionSize: Int = this.maxTransactionSize, modifiedTime: Instant = this.modifiedTime, epoch: Int = this.epoch, whitelistedContractImplementations: Map&lt;String, List&lt;AttachmentId&gt;&gt; = this.whitelistedContractImplementations )</ID>
<ID>LongParameterList:NetworkParameters.kt$NetworkParameters$(minimumPlatformVersion: Int = this.minimumPlatformVersion, notaries: List&lt;NotaryInfo&gt; = this.notaries, maxMessageSize: Int = this.maxMessageSize, maxTransactionSize: Int = this.maxTransactionSize, modifiedTime: Instant = this.modifiedTime, epoch: Int = this.epoch, whitelistedContractImplementations: Map&lt;String, List&lt;AttachmentId&gt;&gt; = this.whitelistedContractImplementations, eventHorizon: Duration = this.eventHorizon )</ID>
<ID>LongParameterList:NodeParameters.kt$NodeParameters$( providedName: CordaX500Name?, rpcUsers: List&lt;User&gt;, verifierType: VerifierType, customOverrides: Map&lt;String, Any?&gt;, startInSameProcess: Boolean?, maximumHeapSize: String )</ID>
@ -770,6 +762,7 @@
<ID>MagicNumber:CordaRPCClient.kt$CordaRPCClient$128</ID>
<ID>MagicNumber:CordaRPCClient.kt$CordaRPCClientConfiguration$3</ID>
<ID>MagicNumber:CordaRPCClient.kt$CordaRPCClientConfiguration$5</ID>
<ID>MagicNumber:CordaSSHAuthInfo.kt$CordaSSHAuthInfo$10</ID>
<ID>MagicNumber:CordaSecurityProvider.kt$CordaSecurityProvider$0.1</ID>
<ID>MagicNumber:Crypto.kt$Crypto$2048</ID>
<ID>MagicNumber:Crypto.kt$Crypto$256</ID>
@ -1080,6 +1073,7 @@
<ID>MagicNumber:RPCDriver.kt$RandomRpcUser.Companion$100</ID>
<ID>MagicNumber:RPCDriver.kt$RandomRpcUser.Companion$3</ID>
<ID>MagicNumber:RPCDriver.kt$RandomRpcUser.Companion$4</ID>
<ID>MagicNumber:RPCPermissionResolver.kt$RPCPermissionResolver$20</ID>
<ID>MagicNumber:RPCServer.kt$RPCServer$5</ID>
<ID>MagicNumber:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps$4</ID>
<ID>MagicNumber:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ErrorInterceptingHandler$1000</ID>
@ -1243,8 +1237,7 @@
<ID>SpreadOperator:AssertingTestDatabaseContext.kt$AssertingTestDatabaseContext$(*expectedScripts)</ID>
<ID>SpreadOperator:AttachmentDemo.kt$(*args)</ID>
<ID>SpreadOperator:AttachmentsClassLoaderStaticContractTests.kt$AttachmentsClassLoaderStaticContractTests$(*packages.toTypedArray())</ID>
<ID>SpreadOperator:AuthenticatedRpcOpsProxy.kt$(methodName, *(args.map(Class&lt;*&gt;::getName).toTypedArray()))</ID>
<ID>SpreadOperator:AuthenticatedRpcOpsProxy.kt$AuthenticatedRpcOpsProxy$(logicType, *args)</ID>
<ID>SpreadOperator:AuthenticatedRpcOpsProxy.kt$AuthenticatedRpcOpsProxy.PermissionsEnforcingInvocationHandler$(methodFullName(method), *(args.map(Class&lt;*&gt;::getName).toTypedArray()))</ID>
<ID>SpreadOperator:AzureInstantiator.kt$AzureInstantiator$(*portsToOpen.toIntArray())</ID>
<ID>SpreadOperator:ByteArrays.kt$OpaqueBytes.Companion$(*b)</ID>
<ID>SpreadOperator:CertificateRevocationListNodeTests.kt$CertificateRevocationListNodeTests$(newNodeCert, INTERMEDIATE_CA.certificate, *nodeKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }.drop(2).toTypedArray())</ID>
@ -1350,8 +1343,7 @@
<ID>SpreadOperator:PropertyValidationTest.kt$PropertyValidationTest$(*key.split(".").toTypedArray(), nestedKey)</ID>
<ID>SpreadOperator:RPCClient.kt$RPCClient$(*haPoolTransportConfigurations.toTypedArray())</ID>
<ID>SpreadOperator:RPCDriver.kt$RandomRpcUser.Companion$(handle.proxy, *arguments.toTypedArray())</ID>
<ID>SpreadOperator:RPCOpsWithContext.kt$(cordaRPCOps, *(args ?: arrayOf()))</ID>
<ID>SpreadOperator:RPCSecurityManagerTest.kt$RPCSecurityManagerTest$(request.first(), *args)</ID>
<ID>SpreadOperator:RPCSecurityManagerTest.kt$RPCSecurityManagerTest$(methodName, *args)</ID>
<ID>SpreadOperator:RPCServer.kt$RPCServer$(invocationTarget.instance, *arguments.toTypedArray())</ID>
<ID>SpreadOperator:ReactiveArtemisConsumer.kt$ReactiveArtemisConsumer.Companion$(queueName, *queueNames)</ID>
<ID>SpreadOperator:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ErrorInterceptingHandler$(reconnectingRPCConnection.proxy, *(args ?: emptyArray()))</ID>
@ -1379,7 +1371,6 @@
<ID>SpreadOperator:X509Utilities.kt$X509Utilities$(*certificates)</ID>
<ID>ThrowsCount:AMQPTypeIdentifierParser.kt$AMQPTypeIdentifierParser$// Make sure our inputs aren't designed to blow things up. private fun validate(typeString: String)</ID>
<ID>ThrowsCount:AbstractNode.kt$AbstractNode$private fun installCordaServices()</ID>
<ID>ThrowsCount:AbstractNode.kt$fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set&lt;MappedSchema&gt;, metricRegistry: MetricRegistry? = null, cordappLoader: CordappLoader? = null, currentDir: Path? = null, ourName: CordaX500Name)</ID>
<ID>ThrowsCount:ArtemisMessagingServer.kt$ArtemisMessagingServer$// TODO: Maybe wrap [IOException] on a key store load error so that it's clearly splitting key store loading from // Artemis IO errors @Throws(IOException::class, AddressBindingException::class, KeyStoreException::class) private fun configureAndStartServer()</ID>
<ID>ThrowsCount:BrokerJaasLoginModule.kt$BaseBrokerJaasLoginModule$@Suppress("DEPRECATION") // should use java.security.cert.X509Certificate protected fun getUsernamePasswordAndCerts(): Triple&lt;String, String, Array&lt;javax.security.cert.X509Certificate&gt;?&gt;</ID>
<ID>ThrowsCount:CheckpointVerifier.kt$CheckpointVerifier$ fun verifyCheckpointsCompatible( checkpointStorage: CheckpointStorage, currentCordapps: List&lt;Cordapp&gt;, platformVersion: Int, serviceHub: ServiceHub, tokenizableServices: List&lt;Any&gt; )</ID>
@ -1403,7 +1394,6 @@
<ID>ThrowsCount:PropertyDescriptor.kt$PropertyDescriptor$ fun validate()</ID>
<ID>ThrowsCount:RPCApi.kt$RPCApi.ServerToClient.Companion$fun fromClientMessage(context: SerializationContext, message: ClientMessage): ServerToClient</ID>
<ID>ThrowsCount:RPCServer.kt$RPCServer$private fun invokeRpc(context: RpcAuthContext, inMethodName: String, arguments: List&lt;Any?&gt;): Try&lt;Any&gt;</ID>
<ID>ThrowsCount:SchemaMigration.kt$SchemaMigration$private fun doRunMigration( run: Boolean, check: Boolean, existingCheckpoints: Boolean? = null )</ID>
<ID>ThrowsCount:ServicesForResolutionImpl.kt$ServicesForResolutionImpl$// We may need to recursively chase transactions if there are notary changes. fun inner(stateRef: StateRef, forContractClassName: String?): Attachment</ID>
<ID>ThrowsCount:SignedNodeInfo.kt$SignedNodeInfo$// TODO Add root cert param (or TrustAnchor) to make sure all the identities belong to the same root fun verified(): NodeInfo</ID>
<ID>ThrowsCount:SignedTransaction.kt$SignedTransaction$@DeleteForDJVM private fun resolveAndCheckNetworkParameters(services: ServiceHub)</ID>
@ -1466,7 +1456,6 @@
<ID>TooGenericExceptionCaught:Eventually.kt$e: Exception</ID>
<ID>TooGenericExceptionCaught:Expect.kt$exception: Exception</ID>
<ID>TooGenericExceptionCaught:Explorer.kt$Explorer$e: Exception</ID>
<ID>TooGenericExceptionCaught:FiberDeserializationCheckingInterceptor.kt$FiberDeserializationChecker$exception: Exception</ID>
<ID>TooGenericExceptionCaught:FinanceJSONSupport.kt$CalendarDeserializer$e: Exception</ID>
<ID>TooGenericExceptionCaught:FlowHandle.kt$FlowProgressHandleImpl$e: Exception</ID>
<ID>TooGenericExceptionCaught:FlowMessaging.kt$FlowMessagingImpl$exception: Exception</ID>
@ -1541,6 +1530,7 @@
<ID>TooGenericExceptionCaught:RPCClient.kt$RPCClient$exception: Throwable</ID>
<ID>TooGenericExceptionCaught:RPCClientProxyHandler.kt$RPCClientProxyHandler$e: Exception</ID>
<ID>TooGenericExceptionCaught:RPCClientProxyHandler.kt$RPCClientProxyHandler$e: RuntimeException</ID>
<ID>TooGenericExceptionCaught:RPCPermissionResolver.kt$RPCPermissionResolver.InterfaceMethodMapCacheLoader$ex: Exception</ID>
<ID>TooGenericExceptionCaught:RPCServer.kt$RPCServer$e: Exception</ID>
<ID>TooGenericExceptionCaught:RPCServer.kt$RPCServer$exception: Throwable</ID>
<ID>TooGenericExceptionCaught:RPCServer.kt$RPCServer$throwable: Throwable</ID>
@ -1630,7 +1620,7 @@
<ID>TooManyFunctions:ContractUpgradeTransactions.kt$ContractUpgradeLedgerTransaction : FullTransactionTransactionWithSignatures</ID>
<ID>TooManyFunctions:CordaRPCOps.kt$CordaRPCOps : RPCOps</ID>
<ID>TooManyFunctions:CordaRPCOps.kt$net.corda.core.messaging.CordaRPCOps.kt</ID>
<ID>TooManyFunctions:CordaRPCOpsImpl.kt$CordaRPCOpsImpl : InternalCordaRPCOpsAutoCloseable</ID>
<ID>TooManyFunctions:CordaRPCOpsImpl.kt$CordaRPCOpsImpl : CordaRPCOpsAutoCloseable</ID>
<ID>TooManyFunctions:Crypto.kt$Crypto</ID>
<ID>TooManyFunctions:CryptoUtils.kt$net.corda.core.crypto.CryptoUtils.kt</ID>
<ID>TooManyFunctions:Currencies.kt$net.corda.finance.Currencies.kt</ID>
@ -1663,8 +1653,6 @@
<ID>TooManyFunctions:P2PMessagingClient.kt$P2PMessagingClient : SingletonSerializeAsTokenMessagingServiceAddressToArtemisQueueResolverServiceStateSupport</ID>
<ID>TooManyFunctions:PathUtils.kt$net.corda.core.internal.PathUtils.kt</ID>
<ID>TooManyFunctions:Perceivable.kt$net.corda.finance.contracts.universal.Perceivable.kt</ID>
<ID>TooManyFunctions:PersistentIdentityService.kt$PersistentIdentityService : SingletonSerializeAsTokenIdentityServiceInternal</ID>
<ID>TooManyFunctions:PersistentNetworkMapCache.kt$PersistentNetworkMapCache : NetworkMapCacheInternalSingletonSerializeAsToken</ID>
<ID>TooManyFunctions:PortfolioApi.kt$PortfolioApi</ID>
<ID>TooManyFunctions:PropertyDescriptor.kt$net.corda.serialization.internal.amqp.PropertyDescriptor.kt</ID>
<ID>TooManyFunctions:QueryCriteria.kt$QueryCriteria$VaultQueryCriteria : CommonQueryCriteria</ID>
@ -1700,8 +1688,6 @@
<ID>UnusedImports:Amount.kt$import net.corda.core.crypto.CompositeKey</ID>
<ID>UnusedImports:Amount.kt$import net.corda.core.identity.Party</ID>
<ID>UnusedImports:DummyLinearStateSchemaV1.kt$import net.corda.core.contracts.ContractState</ID>
<ID>UnusedImports:InternalTestUtils.kt$import java.nio.file.Files</ID>
<ID>UnusedImports:InternalTestUtils.kt$import net.corda.nodeapi.internal.loadDevCaTrustStore</ID>
<ID>UnusedImports:NetworkMap.kt$import net.corda.core.node.NodeInfo</ID>
<ID>UnusedImports:NodeParameters.kt$import net.corda.core.identity.Party</ID>
<ID>UnusedImports:SerializerFactory.kt$import java.io.NotSerializableException</ID>
@ -1960,7 +1946,6 @@
<ID>WildcardImport:CordaExceptionTest.kt$import org.junit.Assert.*</ID>
<ID>WildcardImport:CordaFutureImplTest.kt$import com.nhaarman.mockito_kotlin.*</ID>
<ID>WildcardImport:CordaInternal.kt$import kotlin.annotation.AnnotationTarget.*</ID>
<ID>WildcardImport:CordaMigration.kt$import net.corda.node.services.persistence.*</ID>
<ID>WildcardImport:CordaModule.kt$import com.fasterxml.jackson.annotation.*</ID>
<ID>WildcardImport:CordaModule.kt$import com.fasterxml.jackson.databind.*</ID>
<ID>WildcardImport:CordaModule.kt$import net.corda.core.contracts.*</ID>
@ -1990,7 +1975,6 @@
<ID>WildcardImport:DBTransactionStorage.kt$import javax.persistence.*</ID>
<ID>WildcardImport:DBTransactionStorage.kt$import net.corda.core.serialization.*</ID>
<ID>WildcardImport:DBTransactionStorage.kt$import net.corda.nodeapi.internal.persistence.*</ID>
<ID>WildcardImport:DBTransactionStorageTests.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:DeleteForDJVM.kt$import kotlin.annotation.AnnotationTarget.*</ID>
<ID>WildcardImport:DemoBench.kt$import tornadofx.*</ID>
<ID>WildcardImport:DemoBenchNodeInfoFilesCopier.kt$import tornadofx.*</ID>
@ -2008,7 +1992,6 @@
<ID>WildcardImport:DigitalSignatureWithCert.kt$import java.security.cert.*</ID>
<ID>WildcardImport:DistributedServiceTests.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:DockerInstantiator.kt$import com.github.dockerjava.api.model.*</ID>
<ID>WildcardImport:DriverDSLImpl.kt$import net.corda.testing.driver.*</ID>
<ID>WildcardImport:DummyContract.kt$import net.corda.core.contracts.*</ID>
<ID>WildcardImport:DummyContractV2.kt$import net.corda.core.contracts.*</ID>
<ID>WildcardImport:DummyContractV3.kt$import net.corda.core.contracts.*</ID>
@ -2036,12 +2019,10 @@
<ID>WildcardImport:FlowCheckpointCordapp.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowCheckpointVersionNodeStartupCheckTest.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowCheckpointVersionNodeStartupCheckTest.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:FlowFrameworkPersistenceTests.kt$import net.corda.testing.node.internal.*</ID>
<ID>WildcardImport:FlowFrameworkTripartyTests.kt$import net.corda.testing.node.internal.*</ID>
<ID>WildcardImport:FlowLogicRefFactoryImpl.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowMatchers.kt$import net.corda.coretesting.internal.matchers.*</ID>
<ID>WildcardImport:FlowOverrideTests.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowRetryTest.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowStackSnapshotTest.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowStateMachine.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowsDrainingModeContentionTest.kt$import net.corda.core.flows.*</ID>
@ -2087,7 +2068,6 @@
<ID>WildcardImport:InternalMockNetwork.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:InternalMockNetwork.kt$import net.corda.node.services.config.*</ID>
<ID>WildcardImport:InternalMockNetwork.kt$import net.corda.testing.node.*</ID>
<ID>WildcardImport:InternalTestUtils.kt$import net.corda.core.contracts.*</ID>
<ID>WildcardImport:IssuerModel.kt$import tornadofx.*</ID>
<ID>WildcardImport:JVMConfig.kt$import tornadofx.*</ID>
<ID>WildcardImport:JacksonSupport.kt$import com.fasterxml.jackson.core.*</ID>
@ -2187,7 +2167,6 @@
<ID>WildcardImport:NodeAttachmentServiceTest.kt$import org.assertj.core.api.Assertions.*</ID>
<ID>WildcardImport:NodeAttachmentTrustCalculator.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NodeBasedTest.kt$import net.corda.node.services.config.*</ID>
<ID>WildcardImport:NodeController.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NodeController.kt$import tornadofx.*</ID>
<ID>WildcardImport:NodeControllerTest.kt$import kotlin.test.*</ID>
<ID>WildcardImport:NodeData.kt$import tornadofx.*</ID>
@ -2197,7 +2176,6 @@
<ID>WildcardImport:NodeInterestRates.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:NodeInterestRatesTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:NodeInterestRatesTest.kt$import org.junit.Assert.*</ID>
<ID>WildcardImport:NodeProcess.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NodeRegistrationTest.kt$import javax.ws.rs.*</ID>
<ID>WildcardImport:NodeSchedulerService.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NodeSchedulerServiceTest.kt$import com.nhaarman.mockito_kotlin.*</ID>

View File

@ -129,7 +129,7 @@ class ArtemisRpcTests {
private fun <OPS : RPCOps> InternalRPCMessagingClient.start(ops: OPS, securityManager: RPCSecurityManager, brokerControl: ActiveMQServerControl) {
apply {
init(ops, securityManager, TestingNamedCacheFactory())
init(listOf(ops), securityManager, TestingNamedCacheFactory())
start(brokerControl)
}
}

View File

@ -11,7 +11,6 @@ import net.corda.core.internal.div
import net.corda.core.internal.inputStream
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.list
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.readFully
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
@ -21,6 +20,7 @@ import net.corda.node.services.statemachine.CountUpDownLatch
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.checkpoint.CheckpointRpcHelper.checkpointsRpc
import net.corda.testing.node.User
import net.corda.testing.node.internal.enclosedCordapp
import org.junit.Test
@ -44,15 +44,14 @@ class DumpCheckpointsTest {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use {
val proxy = it.proxy as InternalCordaRPCOps
// 1 for GetNumberOfCheckpointsFlow itself
val checkPointCountFuture = proxy.startFlow(::GetNumberOfCheckpointsFlow).returnValue
val checkPointCountFuture = it.proxy.startFlow(::GetNumberOfCheckpointsFlow).returnValue
val logDirPath = nodeAHandle.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
logDirPath.createDirectories()
dumpCheckPointLatch.await()
proxy.dumpCheckpoints()
nodeAHandle.checkpointsRpc.use { checkpointRPCOps -> checkpointRPCOps.dumpCheckpoints() }
flowProceedLatch.countDown()
assertEquals(1, checkPointCountFuture.get())

View File

@ -37,7 +37,8 @@ import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps
import net.corda.core.internal.messaging.FlowManagerRPCOps
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.rootMessage
import net.corda.core.internal.uncheckedCast
@ -71,6 +72,8 @@ import net.corda.djvm.source.EmptyApi
import net.corda.djvm.source.UserSource
import net.corda.node.CordaClock
import net.corda.node.VersionInfo
import net.corda.node.internal.attachments.AttachmentTrustInfoRPCOpsImpl
import net.corda.node.internal.checkpoints.FlowManagerRPCOpsImpl
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappConfigFileProvider
import net.corda.node.internal.cordapp.CordappProviderImpl
@ -394,21 +397,28 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return this
}
/** The implementation of the [CordaRPCOps] interface used by this node. */
open fun makeRPCOps(cordappLoader: CordappLoader, checkpointDumper: CheckpointDumperImpl): CordaRPCOps {
val ops: InternalCordaRPCOps = CordaRPCOpsImpl(
/** The implementation of the [RPCOps] interfaces used by this node. */
open fun makeRPCOps(cordappLoader: CordappLoader): List<RPCOps> {
val cordaRPCOpsImpl = Pair(CordaRPCOps::class.java, CordaRPCOpsImpl(
services,
smm,
flowStarter,
checkpointDumper
flowStarter
) {
shutdownExecutor.submit(::stop)
}.also { it.closeOnStop() }
val proxies = mutableListOf<(InternalCordaRPCOps) -> InternalCordaRPCOps>()
// Mind that order is relevant here.
proxies += ::AuthenticatedRpcOpsProxy
proxies += { ThreadContextAdjustingRpcOpsProxy(it, cordappLoader.appClassLoader) }
return proxies.fold(ops) { delegate, decorate -> decorate(delegate) }
}.also { it.closeOnStop() })
val checkpointRPCOpsImpl = Pair(FlowManagerRPCOps::class.java, FlowManagerRPCOpsImpl(checkpointDumper))
val attachmentTrustInfoRPCOps = Pair(AttachmentTrustInfoRPCOps::class.java, AttachmentTrustInfoRPCOpsImpl(services.attachmentTrustCalculator))
return listOf(cordaRPCOpsImpl, checkpointRPCOpsImpl, attachmentTrustInfoRPCOps).map { rpcOpsImplPair ->
// Mind that order of proxies is important
val targetInterface = rpcOpsImplPair.first
val stage1Proxy = AuthenticatedRpcOpsProxy.proxy(rpcOpsImplPair.second, targetInterface)
val stage2Proxy = ThreadContextAdjustingRpcOpsProxy.proxy(stage1Proxy, targetInterface, cordappLoader.appClassLoader)
stage2Proxy
}
}
private fun quasarExcludePackages(nodeConfiguration: NodeConfiguration) {
@ -513,7 +523,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
installCoreFlows()
registerCordappFlows()
services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
val rpcOps = makeRPCOps(cordappLoader, checkpointDumper)
val rpcOps = makeRPCOps(cordappLoader)
startShell()
networkMapClient?.start(trustRoot)
@ -623,7 +633,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
/** Subclasses must override this to create a "started" node of the desired type, using the provided machinery. */
abstract fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): S
abstract fun createStartedNode(nodeInfo: NodeInfo, rpcOps: List<RPCOps>, notaryService: NotaryService?): S
private fun verifyCheckpointsCompatible(tokenizableServices: List<Any>) {
try {
@ -1035,7 +1045,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
protected abstract fun makeMessagingService(): MessagingService
protected abstract fun startMessagingService(rpcOps: RPCOps,
protected abstract fun startMessagingService(rpcOps: List<RPCOps>,
nodeInfo: NodeInfo,
myNotaryIdentity: PartyAndCertificate?,
networkParameters: NetworkParameters)

View File

@ -18,12 +18,11 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentTrustInfo
import net.corda.core.internal.FlowStateMachineHandle
import net.corda.core.internal.RPC_UPLOADER
import net.corda.core.internal.STRUCTURAL_STEP_PREFIX
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.sign
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowHandleImpl
@ -54,7 +53,6 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.rpc.CheckpointDumperImpl
import net.corda.node.services.rpc.context
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.nodeapi.exceptions.NonRpcFlowException
@ -75,9 +73,8 @@ internal class CordaRPCOpsImpl(
private val services: ServiceHubInternal,
private val smm: StateMachineManager,
private val flowStarter: FlowStarter,
private val checkpointDumper: CheckpointDumperImpl,
private val shutdownNode: () -> Unit
) : InternalCordaRPCOps, AutoCloseable {
) : CordaRPCOps, AutoCloseable {
private companion object {
private val logger = loggerFor<CordaRPCOpsImpl>()
@ -157,13 +154,6 @@ internal class CordaRPCOpsImpl(
return services.validatedTransactions.track()
}
override fun dumpCheckpoints() = checkpointDumper.dumpCheckpoints()
override val attachmentTrustInfos: List<AttachmentTrustInfo>
get() {
return services.attachmentTrustCalculator.calculateAllTrustInfo()
}
override fun stateMachinesSnapshot(): List<StateMachineInfo> {
val (snapshot, updates) = stateMachinesFeed()
updates.notUsed()

View File

@ -22,7 +22,6 @@ import net.corda.core.internal.errors.AddressBindingException
import net.corda.core.internal.getJavaUpdateVersion
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.notary.NotaryService
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
@ -140,7 +139,7 @@ open class Node(configuration: NodeConfiguration,
allowHibernateToManageAppSchema = allowHibernateToManageAppSchema
) {
override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): NodeInfo =
override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: List<RPCOps>, notaryService: NotaryService?): NodeInfo =
nodeInfo
companion object {
@ -340,7 +339,7 @@ open class Node(configuration: NodeConfiguration,
)
}
override fun startMessagingService(rpcOps: RPCOps, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) {
override fun startMessagingService(rpcOps: List<RPCOps>, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) {
require(nodeInfo.legalIdentities.size in 1..2) { "Currently nodes must have a primary address and optionally one serviced address" }
network as P2PMessagingClient

View File

@ -0,0 +1,16 @@
package net.corda.node.internal.attachments
import net.corda.core.internal.AttachmentTrustCalculator
import net.corda.core.internal.AttachmentTrustInfo
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps
class AttachmentTrustInfoRPCOpsImpl(private val attachmentTrustCalculator: AttachmentTrustCalculator) : AttachmentTrustInfoRPCOps {
override val protocolVersion: Int = PLATFORM_VERSION
override val attachmentTrustInfos: List<AttachmentTrustInfo>
get() {
return attachmentTrustCalculator.calculateAllTrustInfo()
}
}

View File

@ -0,0 +1,15 @@
package net.corda.node.internal.checkpoints
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.messaging.FlowManagerRPCOps
import net.corda.node.services.rpc.CheckpointDumperImpl
/**
* Implementation of [FlowManagerRPCOps]
*/
internal class FlowManagerRPCOpsImpl(private val checkpointDumper: CheckpointDumperImpl) : FlowManagerRPCOps {
override val protocolVersion: Int = PLATFORM_VERSION
override fun dumpCheckpoints() = checkpointDumper.dumpCheckpoints()
}

View File

@ -1,55 +1,72 @@
package net.corda.node.internal.rpc.proxies
import net.corda.client.rpc.PermissionException
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.internal.utilities.InvocationHandlerTemplate
import net.corda.core.messaging.RPCOps
import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName
import net.corda.node.services.rpc.RpcAuthContext
import net.corda.node.services.rpc.rpcContext
import java.lang.reflect.Method
import java.lang.reflect.Proxy
/**
* Implementation of [CordaRPCOps] that checks authorisation.
* Creates proxy that checks entitlements for every RPCOps interface call.
*/
internal class AuthenticatedRpcOpsProxy(private val delegate: InternalCordaRPCOps) : InternalCordaRPCOps by proxy(delegate, ::rpcContext) {
/**
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
* to be present.
*
* TODO: Why is this logic duplicated vs the actual implementation?
*/
override val protocolVersion: Int get() = delegate.nodeInfo().platformVersion
internal object AuthenticatedRpcOpsProxy {
// Need overriding to pass additional `listOf(logicType)` argument for polymorphic `startFlow` permissions.
override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?) = guard("startFlowDynamic", listOf(logicType), ::rpcContext) {
delegate.startFlowDynamic(logicType, *args)
fun <T : RPCOps> proxy(delegate: T, targetInterface: Class<out T>): T {
require(targetInterface.isInterface) { "Interface is expected instead of $targetInterface" }
val handler = PermissionsEnforcingInvocationHandler(delegate, targetInterface)
@Suppress("UNCHECKED_CAST")
return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(targetInterface), handler) as T
}
// Need overriding to pass additional `listOf(logicType)` argument for polymorphic `startFlow` permissions.
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?) = guard("startTrackedFlowDynamic", listOf(logicType), ::rpcContext) {
delegate.startTrackedFlowDynamic(logicType, *args)
}
private class PermissionsEnforcingInvocationHandler(override val delegate: Any, private val clazz: Class<*>) : InvocationHandlerTemplate {
private companion object {
private fun proxy(delegate: InternalCordaRPCOps, context: () -> RpcAuthContext): InternalCordaRPCOps {
val handler = PermissionsEnforcingInvocationHandler(delegate, context)
return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(InternalCordaRPCOps::class.java), handler) as InternalCordaRPCOps
private val exemptMethod = RPCOps::class.java.getMethod("getProtocolVersion")
private val namedInterfaces = setOf(
net.corda.core.messaging.CordaRPCOps::class.java)
private val namedMethods = setOf("startFlowDynamic", "startTrackedFlowDynamic")
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? {
if (method == exemptMethod) {
// "getProtocolVersion" is an exempt from entitlements check as this is the very first *any* RPCClient calls upon login
return super.invoke(proxy, method, arguments)
}
val importantArgs = if (clazz in namedInterfaces && method.name in namedMethods) {
// Normally list of arguments makes no difference when checking entitlements, however when starting flows
// first argument represents a class name of the flow to be started and special handling applies in this case with
// name of the class extracted and passed into `guard` method for entitlements check.
val nonNullArgs = requireNotNull(arguments)
require(nonNullArgs.isNotEmpty())
val firstArgClass = requireNotNull(nonNullArgs[0] as? Class<*>)
listOf(firstArgClass)
} else emptyList()
return guard(method, importantArgs, ::rpcContext) { super.invoke(proxy, method, arguments) }
}
}
private class PermissionsEnforcingInvocationHandler(override val delegate: InternalCordaRPCOps, private val context: () -> RpcAuthContext) : InvocationHandlerTemplate {
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?) = guard(method.name, context) { super.invoke(proxy, method, arguments) }
private fun <RESULT> guard(method: Method, args: List<Class<*>>, context: () -> RpcAuthContext, action: () -> RESULT): RESULT {
if (!context().isPermitted(methodFullName(method), *(args.map(Class<*>::getName).toTypedArray()))) {
throw PermissionException("User not authorized to perform RPC call $method with target $args")
} else {
return action()
}
}
}
}
private fun <RESULT> guard(methodName: String, context: () -> RpcAuthContext, action: () -> RESULT) = guard(methodName, emptyList(), context, action)
object RpcAuthHelper {
const val INTERFACE_SEPARATOR = "#"
private fun <RESULT> guard(methodName: String, args: List<Class<*>>, context: () -> RpcAuthContext, action: () -> RESULT): RESULT {
if (!context().isPermitted(methodName, *(args.map(Class<*>::getName).toTypedArray()))) {
throw PermissionException("User not authorized to perform RPC call $methodName with target $args")
} else {
return action()
fun methodFullName(method: Method):String = methodFullName(method.declaringClass, method.name)
fun methodFullName(clazz: Class<*>, methodName: String): String {
require(clazz.isInterface) { "Must be an interface: $clazz"}
require(RPCOps::class.java.isAssignableFrom(clazz)) { "Must be assignable from RPCOps: $clazz" }
return clazz.name + INTERFACE_SEPARATOR + methodName
}
}

View File

@ -1,30 +1,28 @@
package net.corda.node.internal.rpc.proxies
import net.corda.core.internal.executeWithThreadContextClassLoader
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.core.internal.utilities.InvocationHandlerTemplate
import java.lang.reflect.Method
import java.lang.reflect.Proxy
/**
* A [CordaRPCOps] proxy that adjusts the thread context's class loader temporarily on every invocation with the provided classloader.
* A proxy that adjusts the thread context's class loader temporarily on every invocation of supplied interface with the provided classloader.
* As an example, this can be used to work-around cases, where 3rd party libraries prioritise the thread context's class loader over the current one,
* without sensible fallbacks to the classloader of the current instance.
* If clients' CorDapps use one of these libraries, this temporary adjustment can ensure that any provided classes from these libraries will be available during RPC calls.
*/
internal class ThreadContextAdjustingRpcOpsProxy(private val delegate: InternalCordaRPCOps, private val classLoader: ClassLoader) : InternalCordaRPCOps by proxy(delegate, classLoader) {
private companion object {
private fun proxy(delegate: InternalCordaRPCOps, classLoader: ClassLoader): InternalCordaRPCOps {
val handler = ThreadContextAdjustingRpcOpsProxy.ThreadContextAdjustingInvocationHandler(delegate, classLoader)
return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(InternalCordaRPCOps::class.java), handler) as InternalCordaRPCOps
}
internal object ThreadContextAdjustingRpcOpsProxy {
fun <T : RPCOps> proxy(delegate: T, clazz: Class<out T>, classLoader: ClassLoader): T {
require(clazz.isInterface) { "Interface is expected instead of $clazz" }
val handler = ThreadContextAdjustingInvocationHandler(delegate, classLoader)
@Suppress("UNCHECKED_CAST")
return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(clazz), handler) as T
}
private class ThreadContextAdjustingInvocationHandler(override val delegate: InternalCordaRPCOps, private val classLoader: ClassLoader) : InvocationHandlerTemplate {
internal class ThreadContextAdjustingInvocationHandler(override val delegate: Any, private val classLoader: ClassLoader) : InvocationHandlerTemplate {
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? {
return executeWithThreadContextClassLoader(this.classLoader) { super.invoke(proxy, method, arguments) }
}
}
}

View File

@ -0,0 +1,175 @@
package net.corda.node.internal.security
import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import net.corda.core.internal.toMultiMap
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.node.internal.rpc.proxies.RpcAuthHelper.INTERFACE_SEPARATOR
import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName
import org.apache.shiro.authz.Permission
import org.apache.shiro.authz.permission.PermissionResolver
import org.slf4j.LoggerFactory
import java.lang.reflect.Method
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaMethod
/*
* A [org.apache.shiro.authz.permission.PermissionResolver] implementation for RPC permissions.
* Provides a method to construct an [RPCPermission] instance from its string representation
* in the form used by a Node admin.
*
* Currently valid permission strings have the forms:
*
* - `ALL`: allowing all type of RPC calls
*
* - `InvokeRpc.$RPCMethodName`: allowing to call a given RPC method without restrictions on its arguments.
*
* - `StartFlow.$FlowClassName`: allowing to call a `startFlow*` RPC method targeting a Flow instance
* of a given class
*/
internal object RPCPermissionResolver : PermissionResolver {
private val logger = LoggerFactory.getLogger(RPCPermissionResolver::class.java)
private const val SEPARATOR = '.'
private const val NEW_STYLE_SEP = ":"
private const val ACTION_START_FLOW = "startflow"
private const val ACTION_INVOKE_RPC = "invokerpc"
private const val ACTION_ALL = "all"
private val FLOW_RPC_CALLS = setOf(
"startFlowDynamic",
"startTrackedFlowDynamic",
"startFlow",
"startTrackedFlow")
override fun resolvePermission(representation: String): Permission {
when (representation.substringBefore(SEPARATOR).toLowerCase()) {
ACTION_INVOKE_RPC -> {
val rpcCall = representation.substringAfter(SEPARATOR, "")
require(representation.count { it == SEPARATOR } == 1 && rpcCall.isNotEmpty()) {
"Malformed permission string"
}
val legacyPermitted = when (rpcCall) {
"startFlow" -> setOf("startFlowDynamic", rpcCall)
"startTrackedFlow" -> setOf("startTrackedFlowDynamic", rpcCall)
else -> setOf(rpcCall)
}
return RPCPermission(legacyPermitted.toFullyQualified())
}
ACTION_START_FLOW -> {
val targetFlow = representation.substringAfter(SEPARATOR, "")
require(targetFlow.isNotEmpty()) {
"Missing target flow after StartFlow"
}
return RPCPermission(FLOW_RPC_CALLS.toFullyQualified(), targetFlow)
}
ACTION_ALL -> {
// Leaving empty set of targets and actions to match everything
return RPCPermission()
}
else -> return attemptNewStyleParsing(representation)
}
}
private fun Set<String>.toFullyQualified(): Set<String> {
return map { methodFullName(CordaRPCOps::class.java, it) }.toSet()
}
/**
* New style permissions representation:
* 1. Fully qualified form: InvokeRpc:com.fully.qualified.package.CustomClientRpcOps#firstMethod
* 2. All methods of the interface: InvokeRpc:com.fully.qualified.package.CustomClientRpcOps#ALL
* 3. Methods of specific group: InvokeRpc:com.fully.qualified.package.CustomClientRpcOps#READONLY
*/
private fun attemptNewStyleParsing(permAsString: String): Permission {
return when(permAsString.substringBefore(NEW_STYLE_SEP).toLowerCase()) {
ACTION_INVOKE_RPC -> {
val interfaceAndMethods = permAsString.substringAfter(NEW_STYLE_SEP, "")
val interfaceParts = interfaceAndMethods.split(INTERFACE_SEPARATOR)
require(interfaceParts.size == 2) { "Malformed to comply with new style of InvokeRpc: $interfaceAndMethods" }
val methodsMap = requireNotNull(cache.get(interfaceParts[0]))
{ "Method map for ${interfaceParts[0]} must not be null in the cache. There must have been error processing interface. " +
"Please look at the error log lines above." }
val lookupKey = interfaceAndMethods.toLowerCase()
val methods = requireNotNull(methodsMap[lookupKey]) { "Cannot find record for " +
"'$lookupKey' for interface '${interfaceParts[0]}' in $methodsMap. " +
"Please check permissions configuration string '$permAsString' matching class representation." }
RPCPermission(methods)
}
else -> throw IllegalArgumentException("Unable to parse permission as string: $permAsString")
}
}
private val cache: LoadingCache<String, Map<String, Set<String>>> = Caffeine.newBuilder()
.maximumSize(java.lang.Long.getLong("net.corda.node.internal.security.rpc.interface.cacheSize", 20))
.build(InterfaceMethodMapCacheLoader())
private class InterfaceMethodMapCacheLoader : CacheLoader<String, Map<String, Set<String>>> {
override fun load(interfaceName: String): Map<String, Set<String>>? {
return try {
inspectInterface(interfaceName)
} catch (ex: Exception) {
logger.error("Unexpected error when populating cache for $interfaceName", ex)
null
}
}
}
/**
* Returns a map where key is either:
* - fully qualified interface method name;
* or
* - Wildcard string representing the group of methods like: ALL, READ_ONLY, etc.
* Value is always a set of fully qualified method names.
*/
internal fun inspectInterface(interfaceName: String): Map<String, Set<String>> {
val interfaceClass = Class.forName(interfaceName).kotlin
require(interfaceClass.java.isInterface) { "Must be an interface: $interfaceClass"}
require(RPCOps::class.java.isAssignableFrom(interfaceClass.java)) { "Must be assignable from RPCOps: $interfaceClass" }
val membersPairs = interfaceClass.members.flatMap { member ->
when(member) {
is KFunction -> {
val method = member.javaMethod
if (method != null) {
processMethod(method, interfaceClass)
} else {
logger.info("KFunction $member does not have Java representation - ignoring")
emptyList()
}
}
is KProperty -> {
val method = member.getter.javaMethod
if (method != null) {
processMethod(method, interfaceClass)
} else {
logger.info("KProperty $member does not have Java representation - ignoring")
emptyList()
}
}
else -> {
logger.info("$member is an unhandled type of KCallable - ignoring")
emptyList()
}
}
}
// Pack the pairs into desired resulting data structure
return membersPairs.toMultiMap().mapValues { it.value.toSet() }
}
private fun processMethod(method: Method, interfaceClass: KClass<out Any>): List<Pair<String, String>> {
if(!RPCOps::class.java.isAssignableFrom(method.declaringClass)) {
// To prevent going too deep to Object level
return emptyList()
}
val allKey = methodFullName(interfaceClass.java, ACTION_ALL).toLowerCase()
val methodFullName = methodFullName(method)
return listOf(allKey to methodFullName) + // ALL group
listOf(methodFullName.toLowerCase() to methodFullName) // Full method names individually
}
}

View File

@ -16,10 +16,8 @@ import org.apache.shiro.authc.*
import org.apache.shiro.authc.credential.PasswordMatcher
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher
import org.apache.shiro.authz.AuthorizationInfo
import org.apache.shiro.authz.Permission
import org.apache.shiro.authz.SimpleAuthorizationInfo
import org.apache.shiro.authz.permission.DomainPermission
import org.apache.shiro.authz.permission.PermissionResolver
import org.apache.shiro.cache.CacheManager
import org.apache.shiro.mgt.DefaultSecurityManager
import org.apache.shiro.realm.AuthorizingRealm
@ -106,7 +104,7 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig, cacheFactory: NamedCache
* represented by instances of the [Permission] interface which offers a single method: [implies], to
* test if the 'x implies y' binary predicate is satisfied.
*/
private class RPCPermission : DomainPermission {
internal class RPCPermission : DomainPermission {
/**
* Helper constructor directly setting actions and target field
@ -123,63 +121,6 @@ private class RPCPermission : DomainPermission {
constructor() : super()
}
/*
* A [org.apache.shiro.authz.permission.PermissionResolver] implementation for RPC permissions.
* Provides a method to construct an [RPCPermission] instance from its string representation
* in the form used by a Node admin.
*
* Currently valid permission strings have the forms:
*
* - `ALL`: allowing all type of RPC calls
*
* - `InvokeRpc.$RPCMethodName`: allowing to call a given RPC method without restrictions on its arguments.
*
* - `StartFlow.$FlowClassName`: allowing to call a `startFlow*` RPC method targeting a Flow instance
* of a given class
*/
private object RPCPermissionResolver : PermissionResolver {
private const val SEPARATOR = '.'
private const val ACTION_START_FLOW = "startflow"
private const val ACTION_INVOKE_RPC = "invokerpc"
private const val ACTION_ALL = "all"
private val FLOW_RPC_CALLS = setOf(
"startFlowDynamic",
"startTrackedFlowDynamic",
"startFlow",
"startTrackedFlow")
override fun resolvePermission(representation: String): Permission {
val action = representation.substringBefore(SEPARATOR).toLowerCase()
when (action) {
ACTION_INVOKE_RPC -> {
val rpcCall = representation.substringAfter(SEPARATOR, "")
require(representation.count { it == SEPARATOR } == 1 && !rpcCall.isEmpty()) {
"Malformed permission string"
}
val permitted = when(rpcCall) {
"startFlow" -> setOf("startFlowDynamic", rpcCall)
"startTrackedFlow" -> setOf("startTrackedFlowDynamic", rpcCall)
else -> setOf(rpcCall)
}
return RPCPermission(permitted)
}
ACTION_START_FLOW -> {
val targetFlow = representation.substringAfter(SEPARATOR, "")
require(targetFlow.isNotEmpty()) {
"Missing target flow after StartFlow"
}
return RPCPermission(FLOW_RPC_CALLS, targetFlow)
}
ACTION_ALL -> {
// Leaving empty set of targets and actions to match everything
return RPCPermission()
}
else -> throw IllegalArgumentException("Unknown permission action specifier: $action")
}
}
}
class ShiroAuthorizingSubject(
private val subjectId: PrincipalCollection,
private val manager: DefaultSecurityManager) : AuthorizingSubject {

View File

@ -21,7 +21,7 @@ class InternalRPCMessagingClient(val sslConfig: MutualSslConfiguration, val serv
private var locator: ServerLocator? = null
private var rpcServer: RPCServer? = null
fun init(rpcOps: RPCOps, securityManager: RPCSecurityManager, cacheFactory: NamedCacheFactory) = synchronized(this) {
fun init(rpcOps: List<RPCOps>, securityManager: RPCSecurityManager, cacheFactory: NamedCacheFactory) = synchronized(this) {
val tcpTransport = ArtemisTcpTransport.rpcInternalClientTcpTransport(serverAddress, sslConfig)
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {

View File

@ -14,7 +14,6 @@ import net.corda.core.context.Trace.InvocationId
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.LifeCycle
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext
@ -177,10 +176,10 @@ class RPCServer(
val groupedMethods = with(interfaceClass) {
/*
* Until version 4.3, rpc calls did not include class names.
* Up to this version, only CordaRPCOps and InternalCordaRPCOps were supported.
* Up to this version, only CordaRPCOps was supported.
* So, for these classes methods are registered without their class name as well to preserve backwards compatibility.
*/
if(interfaceClass == CordaRPCOps::class.java || interfaceClass == InternalCordaRPCOps::class.java) {
if (interfaceClass == CordaRPCOps::class.java) {
methods.groupBy { it.name }
} else {
methods.groupBy { interfaceClass.name + CLASS_METHOD_DIVIDER + it.name }

View File

@ -98,7 +98,7 @@ class CordaRPCOpsImplTest {
fun setup() {
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
rpc = aliceNode.rpcOps
rpc = aliceNode.cordaRPCOps
CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), buildSubject("TEST_USER", emptySet())))
mockNet.runNetwork()

View File

@ -3,7 +3,7 @@ package net.corda.node.internal.rpc.proxies
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.CordaRPCOps
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.Mockito.`when`
@ -12,9 +12,9 @@ class ThreadContextAdjustingRpcOpsProxyTest {
private val coreOps = mock<InstrumentedCordaRPCOps>()
private val mockClassloader = mock<ClassLoader>()
private val proxy = ThreadContextAdjustingRpcOpsProxy(coreOps, mockClassloader)
private val proxy = ThreadContextAdjustingRpcOpsProxy.proxy(coreOps, CordaRPCOps::class.java, mockClassloader)
private interface InstrumentedCordaRPCOps : InternalCordaRPCOps {
private interface InstrumentedCordaRPCOps : CordaRPCOps {
fun getThreadContextClassLoader(): ClassLoader = Thread.currentThread().contextClassLoader
}

View File

@ -0,0 +1,57 @@
package net.corda.node.internal.security
import net.corda.core.messaging.RPCOps
import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName
import org.junit.Test
import java.time.ZonedDateTime
import kotlin.test.assertEquals
class RPCPermissionResolverTest {
@Suppress("unused")
interface Alpha : RPCOps {
fun readAlpha() : String
}
@Suppress("unused")
interface Beta : Alpha {
val betaValue : Int
fun writeBeta(foo: Int)
fun nothingSpecial() : Int
}
@Suppress("unused")
interface Gamma : Beta {
fun readGamma() : ZonedDateTime
}
private val readAlphaMethod = methodFullName(Alpha::class.java.getMethod("readAlpha"))
private val readAlphaMethodKey = readAlphaMethod.toLowerCase()
@Test(timeout=300_000)
fun `test Alpha`() {
with(RPCPermissionResolver.inspectInterface(Alpha::class.java.name)) {
assertEquals(3, size, toString()) // protocolVersion, ALL, readAlpha
assertEquals(setOf(readAlphaMethod), this[readAlphaMethodKey], toString())
}
}
@Test(timeout=300_000)
fun `test Beta`() {
with(RPCPermissionResolver.inspectInterface(Beta::class.java.name)) {
assertEquals(6, size, toString()) // protocolVersion, ALL, readAlpha
// and 3 x Beta methods
}
}
@Test(timeout=300_000)
fun `test Gamma`() {
with(RPCPermissionResolver.inspectInterface(Gamma::class.java.name)) {
assertEquals(7, size, toString()) // protocolVersion, ALL, readAlpha,
// 3 x Beta methods and 1 Gamma method
}
}
}

View File

@ -3,6 +3,7 @@ package net.corda.node.services
import net.corda.core.context.AuthServiceId
import net.corda.core.flows.FlowLogic
import net.corda.core.messaging.CordaRPCOps
import net.corda.node.internal.rpc.proxies.RpcAuthHelper
import net.corda.node.internal.security.Password
import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.security.tryAuthenticate
@ -152,14 +153,14 @@ class RPCSecurityManagerTest {
userRealms.tryAuthenticate("user", Password("password"))!!,
userRealms.buildSubject("user"))) {
for (request in permitted) {
val call = request.first()
val methodName = RpcAuthHelper.methodFullName(CordaRPCOps::class.java, request.first())
val args = request.drop(1).toTypedArray()
require(subject.isPermitted(request.first(), *args)) {
"User ${subject.principal} should be permitted $call with target '${request.toList()}'"
require(subject.isPermitted(methodName, *args)) {
"User ${subject.principal} should be permitted $methodName with target '${request.toList()}'"
}
if (args.isEmpty()) {
require(subject.isPermitted(request.first(), "XXX")) {
"User ${subject.principal} should be permitted $call with any target"
require(subject.isPermitted(methodName, "XXX")) {
"User ${subject.principal} should be permitted $methodName with any target"
}
}
}

View File

@ -0,0 +1,24 @@
package net.corda.testing.driver.internal.checkpoint
import net.corda.core.internal.messaging.FlowManagerRPCOps
import net.corda.testing.driver.NodeHandle
object CheckpointRpcHelper {
interface CloseableFlowManagerRPCOps : FlowManagerRPCOps, AutoCloseable
val NodeHandle.checkpointsRpc: CloseableFlowManagerRPCOps
get() {
val user = rpcUsers.first()
val rpcConnection = net.corda.client.rpc.internal.RPCClient<FlowManagerRPCOps>(rpcAddress)
.start(FlowManagerRPCOps::class.java, user.username, user.password)
val proxy = rpcConnection.proxy
return object : CloseableFlowManagerRPCOps, FlowManagerRPCOps by proxy {
override fun close() {
rpcConnection.close()
}
}
}
}

View File

@ -109,7 +109,7 @@ interface TestStartedNode {
val services: StartedNodeServices
val smm: StateMachineManager
val attachments: NodeAttachmentService
val rpcOps: CordaRPCOps
val rpcOpsList: List<RPCOps>
val network: MockNodeMessagingService
val database: CordaPersistence
val notaryService: NotaryService?
@ -135,6 +135,9 @@ interface TestStartedNode {
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>, track: Boolean = false): Observable<T>
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatingFlowClass: Class<out FlowLogic<*>>, initiatedFlowClass: Class<T>, track: Boolean = false): Observable<T>
val cordaRPCOps: CordaRPCOps
get() = rpcOpsList.mapNotNull { it as? CordaRPCOps }.single()
}
open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
@ -306,7 +309,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
override val info: NodeInfo,
override val smm: StateMachineManager,
override val database: CordaPersistence,
override val rpcOps: CordaRPCOps,
override val rpcOpsList: List<RPCOps>,
override val notaryService: NotaryService?) : TestStartedNode {
override fun dispose() = internals.stop()
@ -347,7 +350,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
override val started: TestStartedNode? get() = super.started
override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): TestStartedNode {
override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: List<RPCOps>, notaryService: NotaryService?): TestStartedNode {
return TestStartedNodeImpl(
this,
attachments,
@ -380,7 +383,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
return MockNodeMessagingService(configuration, serverThread).closeOnStop()
}
override fun startMessagingService(rpcOps: RPCOps,
override fun startMessagingService(rpcOps: List<RPCOps>,
nodeInfo: NodeInfo,
myNotaryIdentity: PartyAndCertificate?,
networkParameters: NetworkParameters) {

View File

@ -21,7 +21,6 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.inputStream
import net.corda.core.internal.list
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
@ -49,6 +48,7 @@ import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.NodeHandleInternal
import net.corda.testing.driver.internal.checkpoint.CheckpointRpcHelper.checkpointsRpc
import net.corda.testing.internal.useSslRpcOverrides
import net.corda.testing.node.User
import net.corda.testing.node.internal.enclosedCordapp
@ -299,7 +299,7 @@ class InteractiveShellIntegrationTest {
(alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).createDirectories()
alice.rpc.startFlow(::ExternalOperationFlow)
ExternalOperation.lock.acquire()
InteractiveShell.runDumpCheckpoints(alice.rpc as InternalCordaRPCOps)
alice.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) }
ExternalOperation.lock2.release()
val zipFile = (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() }
@ -322,7 +322,7 @@ class InteractiveShellIntegrationTest {
(alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).createDirectories()
alice.rpc.startFlow(::ExternalAsyncOperationFlow)
ExternalAsyncOperation.lock.acquire()
InteractiveShell.runDumpCheckpoints(alice.rpc as InternalCordaRPCOps)
alice.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) }
ExternalAsyncOperation.future.complete(null)
val zipFile = (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() }
val json = ZipInputStream(zipFile.inputStream()).use { zip ->
@ -350,7 +350,7 @@ class InteractiveShellIntegrationTest {
assertThrows<TimeoutException> {
alice.rpc.startFlow(::WaitForStateConsumptionFlow, stateRefs).returnValue.getOrThrow(10.seconds)
}
InteractiveShell.runDumpCheckpoints(alice.rpc as InternalCordaRPCOps)
alice.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) }
val zipFile = (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() }
val json = ZipInputStream(zipFile.inputStream()).use { zip ->
zip.nextEntry
@ -390,7 +390,7 @@ class InteractiveShellIntegrationTest {
Thread.sleep(5000)
mockRenderPrintWriter()
InteractiveShell.runDumpCheckpoints(aliceNode.rpc as InternalCordaRPCOps)
aliceNode.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) }
val zipFile = (aliceNode.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() }
val json = ZipInputStream(zipFile.inputStream()).use { zip ->

View File

@ -1,14 +1,22 @@
package net.corda.tools.shell;
import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps;
import org.crsh.cli.Command;
import org.crsh.cli.Man;
import org.crsh.cli.Named;
import org.crsh.cli.Usage;
import org.jetbrains.annotations.NotNull;
import static net.corda.tools.shell.InteractiveShell.runAttachmentTrustInfoView;
@Named("attachments")
public class AttachmentShellCommand extends InteractiveShellCommand {
public class AttachmentShellCommand extends InteractiveShellCommand<AttachmentTrustInfoRPCOps> {
@NotNull
@Override
public Class<AttachmentTrustInfoRPCOps> getRpcOpsClass() {
return AttachmentTrustInfoRPCOps.class;
}
@Command
@Man("Displays the trusted CorDapp attachments that have been manually installed or received over the network")

View File

@ -1,14 +1,22 @@
package net.corda.tools.shell;
import net.corda.core.internal.messaging.FlowManagerRPCOps;
import org.crsh.cli.Command;
import org.crsh.cli.Man;
import org.crsh.cli.Named;
import org.crsh.cli.Usage;
import org.jetbrains.annotations.NotNull;
import static net.corda.tools.shell.InteractiveShell.*;
@Named("checkpoints")
public class CheckpointShellCommand extends InteractiveShellCommand {
public class CheckpointShellCommand extends InteractiveShellCommand<FlowManagerRPCOps> {
@NotNull
@Override
public Class<FlowManagerRPCOps> getRpcOpsClass() {
return FlowManagerRPCOps.class;
}
@Command
@Man("Outputs the contents of all checkpoints as json to be manually reviewed")

View File

@ -27,7 +27,7 @@ import static net.corda.tools.shell.InteractiveShell.runStateMachinesView;
"flow constructors (the right one is picked automatically) are then specified using the same syntax as for the run command."
)
@Named("flow")
public class FlowShellCommand extends InteractiveShellCommand {
public class FlowShellCommand extends CordaRpcOpsShellCommand {
private static final Logger logger = LoggerFactory.getLogger(FlowShellCommand.class);

View File

@ -19,7 +19,7 @@ import java.util.List;
import java.util.Optional;
@Named("hashLookup")
public class HashLookupShellCommand extends InteractiveShellCommand {
public class HashLookupShellCommand extends CordaRpcOpsShellCommand {
private static Logger logger = LoggerFactory.getLogger(HashLookupShellCommand.class);
private static final String manualText ="Checks if a transaction matching a specified Id hash value is recorded on this node.\n\n" +
"Both the transaction Id and the hashed value of a transaction Id (as returned by the Notary in case of a double-spend) is a valid input.\n" +

View File

@ -18,7 +18,7 @@ import java.util.Map;
@Man("Allows you to see and update the format that's currently used for the commands' output.")
@Usage("Allows you to see and update the format that's currently used for the commands' output.")
@Named("output-format")
public class OutputFormatCommand extends InteractiveShellCommand {
public class OutputFormatCommand extends CordaRpcOpsShellCommand {
public OutputFormatCommand() {}

View File

@ -25,7 +25,7 @@ import static java.util.Comparator.comparing;
// is the closest you can get in Kotlin to raw types.
@Named("run")
public class RunShellCommand extends InteractiveShellCommand {
public class RunShellCommand extends CordaRpcOpsShellCommand {
private static final Logger logger = LoggerFactory.getLogger(RunShellCommand.class);

View File

@ -0,0 +1,8 @@
package net.corda.tools.shell;
import net.corda.core.messaging.RPCOps;
import org.crsh.auth.AuthInfo;
public interface SshAuthInfo extends AuthInfo {
<T extends RPCOps> T getOrCreateRpcOps(Class<T> rpcOpsClass);
}

View File

@ -13,7 +13,7 @@ import java.util.*;
import static java.util.stream.Collectors.joining;
@Named("start")
public class StartShellCommand extends InteractiveShellCommand {
public class StartShellCommand extends CordaRpcOpsShellCommand {
private static Logger logger = LoggerFactory.getLogger(StartShellCommand.class);

View File

@ -1,14 +1,13 @@
package net.corda.tools.shell
import net.corda.client.rpc.CordaRPCConnection
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.loggerFor
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.crsh.auth.AuthInfo
import org.crsh.auth.AuthenticationPlugin
import org.crsh.plugin.CRaSHPlugin
class CordaAuthenticationPlugin(private val makeRPCConnection: (username: String, credential: String) -> CordaRPCConnection) : CRaSHPlugin<AuthenticationPlugin<String>>(), AuthenticationPlugin<String> {
internal class CordaAuthenticationPlugin(private val rpcOpsProducer: RPCOpsProducer) : CRaSHPlugin<AuthenticationPlugin<String>>(), AuthenticationPlugin<String> {
companion object {
private val logger = loggerFor<CordaAuthenticationPlugin>()
@ -24,9 +23,10 @@ class CordaAuthenticationPlugin(private val makeRPCConnection: (username: String
return AuthInfo.UNSUCCESSFUL
}
try {
val connection = makeRPCConnection(username, credential)
val ops = connection.proxy as InternalCordaRPCOps
return CordaSSHAuthInfo(true, ops, isSsh = true, rpcConn = connection)
val cordaSSHAuthInfo = CordaSSHAuthInfo(rpcOpsProducer, username, credential, isSsh = true)
// We cannot guarantee authentication happened successfully till `RCPClient` session been established, hence doing a dummy call
cordaSSHAuthInfo.getOrCreateRpcOps(CordaRPCOps::class.java).protocolVersion
return cordaSSHAuthInfo
} catch (e: ActiveMQSecurityException) {
logger.warn(e.message)
} catch (e: Exception) {

View File

@ -8,6 +8,6 @@ class CordaDisconnectPlugin : CRaSHPlugin<DisconnectPlugin>(), DisconnectPlugin
override fun getImplementation() = this
override fun onDisconnect(userName: String?, authInfo: AuthInfo?) {
(authInfo as? CordaSSHAuthInfo)?.rpcConn?.forceClose()
(authInfo as? CordaSSHAuthInfo)?.cleanUp()
}
}

View File

@ -0,0 +1,22 @@
package net.corda.tools.shell
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.type.TypeFactory
import net.corda.core.messaging.CordaRPCOps
internal abstract class CordaRpcOpsShellCommand : InteractiveShellCommand<CordaRPCOps>() {
override val rpcOpsClass: Class<out CordaRPCOps> = CordaRPCOps::class.java
fun objectMapper(classLoader: ClassLoader?): ObjectMapper {
val om = createYamlInputMapper()
if (classLoader != null) {
om.typeFactory = TypeFactory.defaultInstance().withClassLoader(classLoader)
}
return om
}
private fun createYamlInputMapper(): ObjectMapper {
val rpcOps = ops()
return InteractiveShell.createYamlInputMapper(rpcOps)
}
}

View File

@ -1,17 +1,53 @@
package net.corda.tools.shell
import com.fasterxml.jackson.databind.ObjectMapper
import net.corda.client.rpc.CordaRPCConnection
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.tools.shell.InteractiveShell.createYamlInputMapper
import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.RemovalListener
import com.google.common.util.concurrent.MoreExecutors
import net.corda.client.rpc.RPCConnection
import net.corda.core.internal.utilities.InvocationHandlerTemplate
import net.corda.core.messaging.RPCOps
import net.corda.tools.shell.utlities.ANSIProgressRenderer
import org.crsh.auth.AuthInfo
import java.lang.reflect.Proxy
class CordaSSHAuthInfo(val successful: Boolean, val rpcOps: InternalCordaRPCOps, val ansiProgressRenderer: ANSIProgressRenderer? = null,
val isSsh: Boolean = false, val rpcConn: CordaRPCConnection? = null) : AuthInfo {
override fun isSuccessful(): Boolean = successful
internal class CordaSSHAuthInfo(private val rpcOpsProducer: RPCOpsProducer,
private val username: String, private val credential: String, val ansiProgressRenderer: ANSIProgressRenderer? = null,
val isSsh: Boolean = false) : SshAuthInfo {
override fun isSuccessful(): Boolean = true
val yamlInputMapper: ObjectMapper by lazy {
createYamlInputMapper(rpcOps)
/**
* It is necessary to have a cache to prevent creation of too many proxies for the same class. Proxy ensures that RPC connections gracefully
* closed when cache entry is eliminated
*/
private val proxiesCache = Caffeine.newBuilder()
.maximumSize(10)
.removalListener(RemovalListener<Class<out RPCOps>, Pair<RPCOps, RPCConnection<RPCOps>>> { _, value, _ -> value?.second?.close() })
.executor(MoreExecutors.directExecutor())
.build(CacheLoader<Class<out RPCOps>, Pair<RPCOps, RPCConnection<RPCOps>>> { key -> createRpcOps(key) })
override fun <T : RPCOps> getOrCreateRpcOps(rpcOpsClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return proxiesCache.get(rpcOpsClass)!!.first as T
}
fun cleanUp() {
proxiesCache.asMap().forEach {
proxiesCache.invalidate(it.key)
it.value.second.forceClose()
}
}
private fun <T : RPCOps> createRpcOps(rpcOpsClass: Class<out T>): Pair<T, RPCConnection<T>> {
val producerResult = rpcOpsProducer(username, credential, rpcOpsClass)
val anotherProxy = proxyRPCOps(producerResult.proxy, rpcOpsClass)
return anotherProxy to producerResult
}
private fun <T : RPCOps> proxyRPCOps(instance: T, rpcOpsClass: Class<out T>): T {
require(rpcOpsClass.isInterface) { "$rpcOpsClass must be an interface" }
@Suppress("UNCHECKED_CAST")
return Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), object : InvocationHandlerTemplate {
override val delegate = instance
}) as T
}
}

View File

@ -9,11 +9,8 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator
import net.corda.client.jackson.JacksonSupport
import net.corda.client.jackson.StringToMethodCallParser
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.CordaRPCConnection
import net.corda.client.rpc.GracefulReconnect
import net.corda.client.rpc.PermissionException
import net.corda.client.rpc.RPCConnection
import net.corda.client.rpc.internal.RPCUtils.isShutdownMethodName
import net.corda.client.rpc.notUsed
import net.corda.core.CordaException
@ -27,7 +24,8 @@ import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps
import net.corda.core.internal.messaging.FlowManagerRPCOps
import net.corda.core.internal.packageName_
import net.corda.core.internal.rootCause
import net.corda.core.internal.uncheckedCast
@ -72,11 +70,10 @@ import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.UndeclaredThrowableException
import java.nio.file.Path
import java.util.*
import java.util.Properties
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutionException
import java.util.concurrent.Future
import kotlin.collections.ArrayList
import kotlin.concurrent.thread
// TODO: Add command history.
@ -95,9 +92,9 @@ const val STANDALONE_SHELL_PERMISSION = "ALL"
@Suppress("MaxLineLength")
object InteractiveShell {
private val log = LoggerFactory.getLogger(javaClass)
private lateinit var makeRPCConnection: (username: String, password: String) -> CordaRPCConnection
private lateinit var ops: InternalCordaRPCOps
private lateinit var rpcConn: CordaRPCConnection
private lateinit var rpcOpsProducer: RPCOpsProducer
private lateinit var startupValidation: Lazy<CordaRPCOps>
private var rpcConn: RPCConnection<CordaRPCOps>? = null
private var shell: Shell? = null
private var classLoader: ClassLoader? = null
private lateinit var shellConfiguration: ShellConfiguration
@ -113,26 +110,7 @@ object InteractiveShell {
}
fun startShell(configuration: ShellConfiguration, classLoader: ClassLoader? = null, standalone: Boolean = false) {
makeRPCConnection = { username: String, password: String ->
val connection = if (standalone) {
CordaRPCClient(
configuration.hostAndPort,
configuration.ssl,
classLoader
).start(username, password, gracefulReconnect = GracefulReconnect())
} else {
CordaRPCClient(
hostAndPort = configuration.hostAndPort,
configuration = CordaRPCClientConfiguration.DEFAULT.copy(
maxReconnectAttempts = 1
),
sslConfiguration = configuration.ssl,
classLoader = classLoader
).start(username, password)
}
rpcConn = connection
connection
}
rpcOpsProducer = DefaultRPCOpsProducer(configuration, classLoader, standalone)
launchShell(configuration, standalone, classLoader)
}
@ -253,7 +231,7 @@ object InteractiveShell {
// Don't use the Java language plugin (we may not have tools.jar available at runtime), this
// will cause any commands using JIT Java compilation to be suppressed. In CRaSH upstream that
// is only the 'jmx' command.
return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(makeRPCConnection) +
return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(rpcOpsProducer) +
CordaDisconnectPlugin()
}
}
@ -262,15 +240,20 @@ object InteractiveShell {
context.refresh()
this.config = config
start(context)
val rpcOps = { username: String, password: String -> makeRPCConnection(username, password).proxy as InternalCordaRPCOps }
ops = makeRPCOps(rpcOps, localUserName, localUserPassword)
return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, ops,
StdoutANSIProgressRenderer), shellSafety)
startupValidation = lazy {
rpcOpsProducer(localUserName, localUserPassword, CordaRPCOps::class.java).let {
rpcConn = it
it.proxy
}
}
// For local shell create an artificial authInfo with super user permissions
val authInfo = CordaSSHAuthInfo(rpcOpsProducer, localUserName, localUserPassword, StdoutANSIProgressRenderer)
return context.getPlugin(ShellFactory::class.java).create(null, authInfo, shellSafety)
}
}
fun nodeInfo() = try {
ops.nodeInfo()
startupValidation.value.nodeInfo()
} catch (e: UndeclaredThrowableException) {
throw e.cause ?: e
}
@ -572,13 +555,13 @@ object InteractiveShell {
@JvmStatic
fun runAttachmentTrustInfoView(
out: RenderPrintWriter,
rpcOps: InternalCordaRPCOps
rpcOps: AttachmentTrustInfoRPCOps
): Any {
return AttachmentTrustTable(out, rpcOps.attachmentTrustInfos)
}
@JvmStatic
fun runDumpCheckpoints(rpcOps: InternalCordaRPCOps) {
fun runDumpCheckpoints(rpcOps: FlowManagerRPCOps) {
rpcOps.dumpCheckpoints()
}
@ -682,7 +665,7 @@ object InteractiveShell {
latch.await()
// Unsubscribe or we hold up the shutdown
subscription.unsubscribe()
rpcConn.forceClose()
rpcConn?.forceClose()
onExit.invoke()
} catch (e: InterruptedException) {
// Cancelled whilst draining flows. So let's carry on from here
@ -690,7 +673,7 @@ object InteractiveShell {
display { println("...cancelled clean shutdown.") }
}
} catch (e: Exception) {
display { println("RPC failed: ${e.rootCause}", Color.red) }
display { println("RPC failed: ${e.rootCause}", Decoration.bold, Color.red) }
} finally {
InputStreamSerializer.invokeContext = null
InputStreamDeserializer.closeAll()

View File

@ -1,23 +1,24 @@
package net.corda.tools.shell
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.type.TypeFactory
import net.corda.core.messaging.RPCOps
import org.crsh.command.BaseCommand
import org.crsh.shell.impl.command.CRaSHSession
/**
* Simply extends CRaSH BaseCommand to add easy access to the RPC ops class.
*/
open class InteractiveShellCommand : BaseCommand() {
fun ops() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).rpcOps
fun ansiProgressRenderer() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).ansiProgressRenderer
fun objectMapper(classLoader: ClassLoader?): ObjectMapper {
val om = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).yamlInputMapper
if (classLoader != null) {
om.typeFactory = TypeFactory.defaultInstance().withClassLoader(classLoader)
}
return om
internal abstract class InteractiveShellCommand<T : RPCOps> : BaseCommand() {
abstract val rpcOpsClass: Class<out T>
@Suppress("UNCHECKED_CAST")
fun ops(): T {
val cRaSHSession = context.session as CRaSHSession
val authInfo = cRaSHSession.authInfo as SshAuthInfo
return authInfo.getOrCreateRpcOps(rpcOpsClass)
}
fun ansiProgressRenderer() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).ansiProgressRenderer
fun isSsh() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).isSsh
}

View File

@ -0,0 +1,49 @@
package net.corda.tools.shell
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.GracefulReconnect
import net.corda.client.rpc.RPCConnection
import net.corda.client.rpc.internal.RPCClient
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
internal interface RPCOpsProducer {
/**
* Returns [RPCConnection] of underlying proxy. Proxy can be obtained at any time by calling [RPCConnection.proxy]
*/
operator fun <T : RPCOps> invoke(username: String?, credential: String?, rpcOpsClass: Class<T>) : RPCConnection<T>
}
internal class DefaultRPCOpsProducer(private val configuration: ShellConfiguration, private val classLoader: ClassLoader? = null, private val standalone: Boolean) : RPCOpsProducer {
override fun <T : RPCOps> invoke(username: String?, credential: String?, rpcOpsClass: Class<T>): RPCConnection<T> {
return if (rpcOpsClass == CordaRPCOps::class.java) {
// For CordaRPCOps we are using CordaRPCClient
val connection = if (standalone) {
CordaRPCClient(
configuration.hostAndPort,
configuration.ssl,
classLoader
).start(username!!, credential!!, gracefulReconnect = GracefulReconnect())
} else {
CordaRPCClient(
hostAndPort = configuration.hostAndPort,
configuration = CordaRPCClientConfiguration.DEFAULT.copy(
maxReconnectAttempts = 1
),
sslConfiguration = configuration.ssl,
classLoader = classLoader
).start(username!!, credential!!)
}
@Suppress("UNCHECKED_CAST")
connection as RPCConnection<T>
} else {
// For other types "plain" RPCClient is used
val rpcClient = RPCClient<T>(configuration.hostAndPort, configuration.ssl)
val connection = rpcClient.start(rpcOpsClass, username!!, credential!!)
connection
}
}
}

View File

@ -1,20 +0,0 @@
package net.corda.tools.shell
import net.corda.core.internal.messaging.InternalCordaRPCOps
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Proxy
fun makeRPCOps(getCordaRPCOps: (username: String, credential: String) -> InternalCordaRPCOps, username: String, credential: String): InternalCordaRPCOps {
val cordaRPCOps: InternalCordaRPCOps by lazy {
getCordaRPCOps(username, credential)
}
return Proxy.newProxyInstance(InternalCordaRPCOps::class.java.classLoader, arrayOf(InternalCordaRPCOps::class.java)) { _, method, args ->
try {
method.invoke(cordaRPCOps, *(args ?: arrayOf()))
} catch (e: InvocationTargetException) {
// Unpack exception.
throw e.targetException
}
} as InternalCordaRPCOps
}

View File

@ -18,7 +18,7 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowProgressHandleImpl
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
@ -42,7 +42,7 @@ import kotlin.test.assertFailsWith
class InteractiveShellTest {
lateinit var inputObjectMapper: ObjectMapper
lateinit var cordaRpcOps: InternalCordaRPCOps
lateinit var cordaRpcOps: CordaRPCOps
lateinit var invocationContext: InvocationContext<Map<Any, Any>>
lateinit var printWriter: RenderPrintWriter