CORDA-311-post PR merged fixes (#2106)

* SSH server integration
This commit is contained in:
Maksymilian Pawlak 2017-11-23 16:34:57 +00:00 committed by GitHub
parent 502d0df630
commit ce9b6c1f18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 101 additions and 226 deletions

View File

@ -348,6 +348,12 @@ abstract class FlowLogic<out T> {
} }
} }
/**
* Returns a pair of the current progress step index (as integer) in steps tree of current [progressTracker], and an observable
* of its upcoming changes.
*
* @return Returns null if this flow has no progress tracker.
*/
fun trackStepsTreeIndex(): DataFeed<Int, Int>? { fun trackStepsTreeIndex(): DataFeed<Int, Int>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe // TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let { return progressTracker?.let {
@ -355,6 +361,12 @@ abstract class FlowLogic<out T> {
} }
} }
/**
* Returns a pair of the current steps tree of current [progressTracker] as pairs of zero-based depth and stringified step
* label and observable of upcoming changes to the structure.
*
* @return Returns null if this flow has no progress tracker.
*/
fun trackStepsTree(): DataFeed<List<Pair<Int,String>>, List<Pair<Int,String>>>? { fun trackStepsTree(): DataFeed<List<Pair<Int,String>>, List<Pair<Int,String>>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe // TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let { return progressTracker?.let {

View File

@ -8,13 +8,17 @@ import rx.Observable
/** /**
* [FlowHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value. * [FlowHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value.
*
* @property id The started state machine's ID.
* @property returnValue A [CordaFuture] of the flow's return value.
*/ */
@DoNotImplement @DoNotImplement
interface FlowHandle<A> : AutoCloseable { interface FlowHandle<A> : AutoCloseable {
/**
* The started state machine's ID.
*/
val id: StateMachineRunId val id: StateMachineRunId
/**
* A [CordaFuture] of the flow's return value.
*/
val returnValue: CordaFuture<A> val returnValue: CordaFuture<A>
/** /**
@ -25,15 +29,23 @@ interface FlowHandle<A> : AutoCloseable {
/** /**
* [FlowProgressHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value. * [FlowProgressHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value.
*
* @property progress The stream of progress tracker events.
*/ */
interface FlowProgressHandle<A> : FlowHandle<A> { interface FlowProgressHandle<A> : FlowHandle<A> {
/**
* The stream of progress tracker events.
*/
val progress: Observable<String> val progress: Observable<String>
/**
* [DataFeed] of current step in the steps tree, see [ProgressTracker]
*/
val stepsTreeIndexFeed: DataFeed<Int, Int>? val stepsTreeIndexFeed: DataFeed<Int, Int>?
/**
* [DataFeed] of current steps tree, see [ProgressTracker]
*/
val stepsTreeFeed: DataFeed<List<Pair<Int, String>>, List<Pair<Int, String>>>? val stepsTreeFeed: DataFeed<List<Pair<Int, String>>, List<Pair<Int, String>>>?
/** /**
* Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up * Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up
* server resources. * server resources.

View File

@ -99,6 +99,7 @@ class ProgressTracker(vararg steps: Step) {
field = value field = value
} }
/** The zero-bases index of the current step in a [allStepsLabels] list */
var stepsTreeIndex: Int = -1 var stepsTreeIndex: Int = -1
private set(value) { private set(value) {
field = value field = value
@ -226,6 +227,10 @@ class ProgressTracker(vararg steps: Step) {
*/ */
val allSteps: List<Pair<Int, Step>> get() = _allStepsCache val allSteps: List<Pair<Int, Step>> get() = _allStepsCache
/**
* A list of all steps label in this ProgressTracker and the children, with the indent level provided starting at zero.
* Note that UNSTARTED is never counted, and DONE is only counted at the calling level.
*/
val allStepsLabels: List<Pair<Int, String>> get() = _allStepsLabels() val allStepsLabels: List<Pair<Int, String>> get() = _allStepsLabels()
private var curChangeSubscription: Subscription? = null private var curChangeSubscription: Subscription? = null
@ -245,8 +250,14 @@ class ProgressTracker(vararg steps: Step) {
*/ */
val changes: Observable<Change> get() = _changes val changes: Observable<Change> get() = _changes
/**
* An observable stream of changes to the [allStepsLabels]
*/
val stepsTreeChanges: Observable<List<Pair<Int,String>>> get() = _stepsTreeChanges val stepsTreeChanges: Observable<List<Pair<Int,String>>> get() = _stepsTreeChanges
/**
* An observable stream of changes to the [stepsTreeIndex]
*/
val stepsTreeIndexChanges: Observable<Int> get() = _stepsTreeIndexChanges val stepsTreeIndexChanges: Observable<Int> get() = _stepsTreeIndexChanges
/** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */ /** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */

View File

@ -98,7 +98,7 @@ class ProgressTrackerTest {
val allSteps = pt.allSteps val allSteps = pt.allSteps
//capture notifications // Capture notifications.
val stepsIndexNotifications = LinkedList<Int>() val stepsIndexNotifications = LinkedList<Int>()
pt.stepsTreeIndexChanges.subscribe { pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it stepsIndexNotifications += it
@ -113,7 +113,7 @@ class ProgressTrackerTest {
assertEquals(step, allSteps[pt.stepsTreeIndex].second) assertEquals(step, allSteps[pt.stepsTreeIndex].second)
} }
//travel tree // Travel tree.
pt.currentStep = SimpleSteps.ONE pt.currentStep = SimpleSteps.ONE
assertCurrentStepsTree(0, SimpleSteps.ONE) assertCurrentStepsTree(0, SimpleSteps.ONE)
@ -126,7 +126,7 @@ class ProgressTrackerTest {
pt.currentStep = SimpleSteps.THREE pt.currentStep = SimpleSteps.THREE
assertCurrentStepsTree(5, SimpleSteps.THREE) assertCurrentStepsTree(5, SimpleSteps.THREE)
//assert no structure changes and proper steps propagation // Assert no structure changes and proper steps propagation.
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5)) assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5))
assertThat(stepsTreeNotification).isEmpty() assertThat(stepsTreeNotification).isEmpty()
} }
@ -135,13 +135,13 @@ class ProgressTrackerTest {
fun `structure changes are pushed down when progress trackers are added`() { fun `structure changes are pushed down when progress trackers are added`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2) pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
//capture notifications // Capture notifications.
val stepsIndexNotifications = LinkedList<Int>() val stepsIndexNotifications = LinkedList<Int>()
pt.stepsTreeIndexChanges.subscribe { pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it stepsIndexNotifications += it
} }
//put current state as a first change for simplicity when asserting // Put current state as a first change for simplicity when asserting.
val stepsTreeNotification = mutableListOf(pt.allStepsLabels) val stepsTreeNotification = mutableListOf(pt.allStepsLabels)
println(pt.allStepsLabels) println(pt.allStepsLabels)
pt.stepsTreeChanges.subscribe { pt.stepsTreeChanges.subscribe {
@ -164,7 +164,7 @@ class ProgressTrackerTest {
assertCurrentStepsTree(9, SimpleSteps.FOUR) assertCurrentStepsTree(9, SimpleSteps.FOUR)
//assert no structure changes and proper steps propagation // Assert no structure changes and proper steps propagation.
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 6, 9)) assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 6, 9))
assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state
} }
@ -173,13 +173,13 @@ class ProgressTrackerTest {
fun `structure changes are pushed down when progress trackers are removed`() { fun `structure changes are pushed down when progress trackers are removed`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2) pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
//capture notifications // Capture notifications.
val stepsIndexNotifications = LinkedList<Int>() val stepsIndexNotifications = LinkedList<Int>()
pt.stepsTreeIndexChanges.subscribe { pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it stepsIndexNotifications += it
} }
//put current state as a first change for simplicity when asserting // Put current state as a first change for simplicity when asserting.
val stepsTreeNotification = mutableListOf(pt.allStepsLabels) val stepsTreeNotification = mutableListOf(pt.allStepsLabels)
pt.stepsTreeChanges.subscribe { pt.stepsTreeChanges.subscribe {
stepsTreeNotification += it stepsTreeNotification += it
@ -199,9 +199,9 @@ class ProgressTrackerTest {
assertCurrentStepsTree(2, BabySteps.UNOS) assertCurrentStepsTree(2, BabySteps.UNOS)
//assert no structure changes and proper steps propagation // Assert no structure changes and proper steps propagation.
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 2)) assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 2))
assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state.
} }
@Test @Test

View File

@ -35,9 +35,9 @@ Shell can also be accessible via SSH. By default SSH server is *disabled*. To en
Authentication and authorization Authentication and authorization
-------------------------------- --------------------------------
SSH require user to login first - using the same users as RPC system. In fact, shell serves as a proxy to RPC and communicates SSH requires users to login first - using the same users as RPC system. In fact, the shell serves as a proxy to RPC and communicates
with node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection with the node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection
and login in. and log in.
Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed`` while starting flows requires Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed`` while starting flows requires
``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows`` in addition to a permission for a particular flow. ``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows`` in addition to a permission for a particular flow.
@ -51,7 +51,7 @@ errors.
Connecting Connecting
---------- ----------
Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually require extra download. Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually requires extra download.
Usual connection syntax is ``ssh user@host -p 2222`` - where ``user`` is a RPC username, and ``-p`` specifies a port parameters - Usual connection syntax is ``ssh user@host -p 2222`` - where ``user`` is a RPC username, and ``-p`` specifies a port parameters -
it's the same as setup in ``node.conf`` file. ``host`` should point to a node hostname, usually ``localhost`` if connecting and it's the same as setup in ``node.conf`` file. ``host`` should point to a node hostname, usually ``localhost`` if connecting and
running node on the same computer. Password will be asked after establishing connection. running node on the same computer. Password will be asked after establishing connection.

View File

@ -139,8 +139,8 @@ class SSHServerTest {
val response = String(Streams.readAll(channel.inputStream)) val response = String(Streams.readAll(channel.inputStream))
//There are ANSI control characters involved, so we want to avoid direct byte to byte matching // There are ANSI control characters involved, so we want to avoid direct byte to byte matching.
assertThat(response.lines()).filteredOn( { it.contains("") && it.contains("Done")}).hasSize(1) assertThat(response.lines()).filteredOn( { it.contains("Done")}).hasSize(1)
} }
} }

View File

@ -4,7 +4,7 @@ package net.corda.node.shell;
import net.corda.core.messaging.CordaRPCOps; import net.corda.core.messaging.CordaRPCOps;
import net.corda.node.utilities.ANSIProgressRenderer; import net.corda.node.utilities.ANSIProgressRenderer;
import net.corda.node.utilities.CRaSHNSIProgressRenderer; import net.corda.node.utilities.CRaSHANSIProgressRenderer;
import org.crsh.cli.*; import org.crsh.cli.*;
import org.crsh.command.*; import org.crsh.command.*;
import org.crsh.text.*; import org.crsh.text.*;
@ -12,7 +12,6 @@ import org.crsh.text.ui.TableElement;
import java.util.*; import java.util.*;
import static net.corda.node.services.messaging.RPCServerKt.CURRENT_RPC_CONTEXT;
import static net.corda.node.shell.InteractiveShell.*; import static net.corda.node.shell.InteractiveShell.*;
@Man( @Man(
@ -49,7 +48,7 @@ public class FlowShellCommand extends InteractiveShellCommand {
return; return;
} }
String inp = input == null ? "" : String.join(" ", input).trim(); String inp = input == null ? "" : String.join(" ", input).trim();
runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) ); runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHANSIProgressRenderer(out) );
} }
@Command @Command

View File

@ -30,7 +30,7 @@ public class RunShellCommand extends InteractiveShellCommand {
return null; return null;
} }
return InteractiveShell.runRPCFromString(command, out, context); return InteractiveShell.runRPCFromString(command, out, context, ops());
} }
private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) { private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {

View File

@ -3,7 +3,7 @@ package net.corda.node.shell;
// A simple forwarder to the "flow start" command, for easier typing. // A simple forwarder to the "flow start" command, for easier typing.
import net.corda.node.utilities.ANSIProgressRenderer; import net.corda.node.utilities.ANSIProgressRenderer;
import net.corda.node.utilities.CRaSHNSIProgressRenderer; import net.corda.node.utilities.CRaSHANSIProgressRenderer;
import org.crsh.cli.*; import org.crsh.cli.*;
import java.util.*; import java.util.*;
@ -14,6 +14,6 @@ public class StartShellCommand extends InteractiveShellCommand {
public void main(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, public void main(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name,
@Usage("The data to pass as input") @Argument(unquote = false) List<String> input) { @Usage("The data to pass as input") @Argument(unquote = false) List<String> input) {
ANSIProgressRenderer ansiProgressRenderer = ansiProgressRenderer(); ANSIProgressRenderer ansiProgressRenderer = ansiProgressRenderer();
FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out)); FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHANSIProgressRenderer(out));
} }
} }

View File

@ -25,7 +25,7 @@ class CordaAuthenticationPlugin(val rpcOps:CordaRPCOps, val userService:RPCUserS
if (user != null && user.password == credential) { if (user != null && user.password == credential) {
val actor = Actor(Actor.Id(username), userService.id, nodeLegalName) val actor = Actor(Actor.Id(username), userService.id, nodeLegalName)
return CordaSSHAuthInfo(true, RPCOpsWithContext(rpcOps, InvocationContext.rpc(actor), RpcPermissions(user.permissions))) return CordaSSHAuthInfo(true, makeRPCOpsWithContext(rpcOps, InvocationContext.rpc(actor), RpcPermissions(user.permissions)))
} }
return AuthInfo.UNSUCCESSFUL; return AuthInfo.UNSUCCESSFUL;

View File

@ -101,10 +101,10 @@ object InteractiveShell {
this.nodeLegalName = configuration.myLegalName this.nodeLegalName = configuration.myLegalName
this.database = database this.database = database
val dir = configuration.baseDirectory val dir = configuration.baseDirectory
val runSshDeamon = configuration.sshd != null val runSshDaemon = configuration.sshd != null
val config = Properties() val config = Properties()
if (runSshDeamon) { if (runSshDaemon) {
val sshKeysDir = dir / "sshkey" val sshKeysDir = dir / "sshkey"
sshKeysDir.toFile().mkdirs() sshKeysDir.toFile().mkdirs()
@ -120,7 +120,7 @@ object InteractiveShell {
ExternalResolver.INSTANCE.addCommand("start", "An alias for 'flow start'", StartShellCommand::class.java) ExternalResolver.INSTANCE.addCommand("start", "An alias for 'flow start'", StartShellCommand::class.java)
shell = ShellLifecycle(dir).start(config) shell = ShellLifecycle(dir).start(config)
if (runSshDeamon) { if (runSshDaemon) {
Node.printBasicNodeInfo("SSH server listening on port", configuration.sshd!!.port.toString()) Node.printBasicNodeInfo("SSH server listening on port", configuration.sshd!!.port.toString())
} }
} }
@ -182,7 +182,7 @@ object InteractiveShell {
context.refresh() context.refresh()
this.config = config this.config = config
start(context) start(context)
return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, RPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer)) return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, makeRPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer))
} }
} }
@ -236,7 +236,7 @@ object InteractiveShell {
try { try {
// Show the progress tracker on the console until the flow completes or is interrupted with a // Show the progress tracker on the console until the flow completes or is interrupted with a
// Ctrl-C keypress. // Ctrl-C keypress.
val stateObservable = runFlowFromString({ clazz,args -> rpcOps.startTrackedFlowDynamic (clazz, *args) }, inputData, clazz) val stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, clazz)
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
ansiProgressRenderer.render(stateObservable, { latch.countDown() }) ansiProgressRenderer.render(stateObservable, { latch.countDown() })
@ -247,7 +247,6 @@ object InteractiveShell {
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
// TODO: When the flow framework allows us to kill flows mid-flight, do so here. // TODO: When the flow framework allows us to kill flows mid-flight, do so here.
} }
} catch (e: NoApplicableConstructor) { } catch (e: NoApplicableConstructor) {
output.println("No matching constructor found:", Color.red) output.println("No matching constructor found:", Color.red)
e.errors.forEach { output.println("- $it", Color.red) } e.errors.forEach { output.println("- $it", Color.red) }
@ -326,7 +325,9 @@ object InteractiveShell {
val (stateMachines, stateMachineUpdates) = proxy.stateMachinesFeed() val (stateMachines, stateMachineUpdates) = proxy.stateMachinesFeed()
val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) } val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) }
val subscriber = FlowWatchPrintingSubscriber(out) val subscriber = FlowWatchPrintingSubscriber(out)
stateMachineUpdates.startWith(currentStateMachines).subscribe(subscriber) database.transaction {
stateMachineUpdates.startWith(currentStateMachines).subscribe(subscriber)
}
var result: Any? = subscriber.future var result: Any? = subscriber.future
if (result is Future<*>) { if (result is Future<*>) {
if (!result.isDone) { if (!result.isDone) {
@ -348,7 +349,7 @@ object InteractiveShell {
} }
@JvmStatic @JvmStatic
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>): Any? { fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>, cordaRPCOps: CordaRPCOps): Any? {
val parser = StringToMethodCallParser(CordaRPCOps::class.java, context.attributes["mapper"] as ObjectMapper) val parser = StringToMethodCallParser(CordaRPCOps::class.java, context.attributes["mapper"] as ObjectMapper)
val cmd = input.joinToString(" ").trim { it <= ' ' } val cmd = input.joinToString(" ").trim { it <= ' ' }
@ -363,7 +364,7 @@ object InteractiveShell {
var result: Any? = null var result: Any? = null
try { try {
InputStreamSerializer.invokeContext = context InputStreamSerializer.invokeContext = context
val call = database.transaction { parser.parse(context.attributes["ops"] as CordaRPCOps, cmd) } val call = database.transaction { parser.parse(cordaRPCOps, cmd) }
result = call.call() result = call.call()
if (result != null && result !is kotlin.Unit && result !is Void) { if (result != null && result !is kotlin.Unit && result !is Void) {
result = printAndFollowRPCResponse(result, out) result = printAndFollowRPCResponse(result, out)

View File

@ -1,205 +1,45 @@
package net.corda.node.shell package net.corda.node.shell
import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext import net.corda.core.context.InvocationContext
import net.corda.core.contracts.ContractState
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.messaging.* import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT
import net.corda.node.services.messaging.RpcAuthContext import net.corda.node.services.messaging.RpcAuthContext
import net.corda.node.services.messaging.RpcPermissions import net.corda.node.services.messaging.RpcPermissions
import java.io.InputStream import java.lang.reflect.InvocationTargetException
import java.security.PublicKey import java.lang.reflect.Proxy
import java.time.Instant
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future import java.util.concurrent.Future
class RPCOpsWithContext(val cordaRPCOps: CordaRPCOps, val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions) : CordaRPCOps { fun makeRPCOpsWithContext(cordaRPCOps: CordaRPCOps, invocationContext:InvocationContext, rpcPermissions: RpcPermissions) : CordaRPCOps {
return Proxy.newProxyInstance(CordaRPCOps::class.java.classLoader, arrayOf(CordaRPCOps::class.java), { proxy, method, args ->
RPCContextRunner(invocationContext, rpcPermissions) {
try {
method.invoke(cordaRPCOps, *(args ?: arrayOf()))
} catch (e: InvocationTargetException) {
// Unpack exception.
throw e.targetException
}
}.get().getOrThrow()
}) as CordaRPCOps
}
private class RPCContextRunner<T>(val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions, val block:() -> T) : Thread() {
class RPCContextRunner<T>(val invocationContext:InvocationContext, val permissions:RpcPermissions, val block:() -> T) : Thread() { private var result: CompletableFuture<T> = CompletableFuture()
private var result: CompletableFuture<T> = CompletableFuture() override fun run() {
override fun run() { CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, rpcPermissions))
CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, permissions)) try {
try { result.complete(block())
result.complete(block()) } catch (e:Throwable) {
} catch (e:Throwable) { result.completeExceptionally(e)
result.completeExceptionally(e) } finally {
}
CURRENT_RPC_CONTEXT.remove() CURRENT_RPC_CONTEXT.remove()
} }
fun get(): Future<T> {
start()
join()
return result
}
} }
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash { fun get(): Future<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachmentWithMetadata(jar, uploader, filename) }.get().getOrThrow() start()
} join()
return result
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.queryAttachments(query, sorting) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByCriteria(contractStateType, criteria) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrack(contractStateType) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByCriteria(criteria, contractStateType) }.get().getOrThrow()
}
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQuery(contractStateType) }.get().getOrThrow()
}
override fun stateMachinesSnapshot(): List<StateMachineInfo> {
return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesSnapshot).get().getOrThrow()
}
override fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate> {
return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesFeed).get().getOrThrow()
}
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): Vault.Page<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow()
}
override fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow()
}
override fun internalVerifiedTransactionsSnapshot(): List<SignedTransaction> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsSnapshot() }.get().getOrThrow()
}
override fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsFeed() }.get().getOrThrow()
}
override fun stateMachineRecordedTransactionMappingSnapshot(): List<StateMachineTransactionMapping> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingSnapshot() }.get().getOrThrow()
}
override fun stateMachineRecordedTransactionMappingFeed(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingFeed() }.get().getOrThrow()
}
override fun networkMapSnapshot(): List<NodeInfo> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapSnapshot() }.get().getOrThrow()
}
override fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapFeed() }.get().getOrThrow()
}
override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startFlowDynamic(logicType, *args) }.get().getOrThrow()
}
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startTrackedFlowDynamic(logicType, *args) }.get().getOrThrow()
}
override fun nodeInfo(): NodeInfo {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfo() }.get().getOrThrow()
}
override fun notaryIdentities(): List<Party> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryIdentities() }.get().getOrThrow()
}
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.addVaultTransactionNote(txnId, txnNote) }.get().getOrThrow()
}
override fun getVaultTransactionNotes(txnId: SecureHash): Iterable<String> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.getVaultTransactionNotes(txnId) }.get().getOrThrow()
}
override fun attachmentExists(id: SecureHash): Boolean {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.attachmentExists(id) }.get().getOrThrow()
}
override fun openAttachment(id: SecureHash): InputStream {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.openAttachment(id) }.get().getOrThrow()
}
override fun uploadAttachment(jar: InputStream): SecureHash {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachment(jar) }.get().getOrThrow()
}
override fun currentNodeTime(): Instant {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.currentNodeTime() }.get().getOrThrow()
}
override fun waitUntilNetworkReady(): CordaFuture<Void?> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.waitUntilNetworkReady() }.get().getOrThrow()
}
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromAnonymous(party) }.get().getOrThrow()
}
override fun partyFromKey(key: PublicKey): Party? {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partyFromKey(key) }.get().getOrThrow()
}
override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromX500Name(x500Name) }.get().getOrThrow()
}
override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryPartyFromX500Name(x500Name) }.get().getOrThrow()
}
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partiesFromName(query, exactMatch) }.get().getOrThrow()
}
override fun registeredFlows(): List<String> {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.registeredFlows() }.get().getOrThrow()
}
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfoFromParty(party) }.get().getOrThrow()
}
override fun clearNetworkMapCache() {
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.clearNetworkMapCache() }.get().getOrThrow()
} }
} }

View File

@ -169,7 +169,7 @@ abstract class ANSIProgressRenderer {
} }
class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() { class CRaSHANSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() {
override fun printLine(line: String) { override fun printLine(line: String) {
renderPrintWriter.println(line) renderPrintWriter.println(line)
@ -181,7 +181,7 @@ class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIPr
} }
override fun setup() { override fun setup() {
//we assume SSH always use ansi // We assume SSH always use ANSI.
usingANSI = true usingANSI = true
} }