mirror of
https://github.com/corda/corda.git
synced 2025-06-04 16:40:57 +00:00
Shell: add support for uploading and downloading attachments.
This commit is contained in:
parent
347224c900
commit
ac90fe724e
@ -18,7 +18,11 @@ import net.corda.testing.node.MockNetwork
|
|||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
|
import java.util.jar.JarEntry
|
||||||
|
import java.util.jar.JarOutputStream
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
@ -134,9 +138,18 @@ class ResolveTransactionsFlowTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun attachment() {
|
fun attachment() {
|
||||||
|
fun makeJar(): InputStream {
|
||||||
|
val bs = ByteArrayOutputStream()
|
||||||
|
val jar = JarOutputStream(bs)
|
||||||
|
jar.putNextEntry(JarEntry("TEST"))
|
||||||
|
jar.write("Some test file".toByteArray())
|
||||||
|
jar.closeEntry()
|
||||||
|
jar.close()
|
||||||
|
return bs.toByteArray().opaque().open()
|
||||||
|
}
|
||||||
// TODO: this operation should not require an explicit transaction
|
// TODO: this operation should not require an explicit transaction
|
||||||
val id = databaseTransaction(a.database) {
|
val id = databaseTransaction(a.database) {
|
||||||
a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
|
a.services.storageService.attachments.importAttachment(makeJar())
|
||||||
}
|
}
|
||||||
val stx2 = makeTransactions(withAttachment = id).second
|
val stx2 = makeTransactions(withAttachment = id).second
|
||||||
val p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)
|
val p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)
|
||||||
|
419
docs/build/html/shell.html
vendored
Normal file
419
docs/build/html/shell.html
vendored
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
<!-- If you edit this, then please make the same changes to layout_for_doc_website.html, as that is used for the web
|
||||||
|
doc site generation which we put analytics tracking on to identify any potential problem pages -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
|
||||||
|
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<title>Shell — R3 Corda latest documentation</title>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="top" title="R3 Corda latest documentation" href="index.html"/>
|
||||||
|
<link rel="next" title="Object Serialization" href="serialization.html"/>
|
||||||
|
<link rel="prev" title="The example CorDapp" href="tutorial-cordapp.html"/>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="_static/js/modernizr.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="wy-body-for-nav" role="document">
|
||||||
|
|
||||||
|
<div class="wy-grid-for-nav">
|
||||||
|
|
||||||
|
|
||||||
|
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
||||||
|
<div class="wy-side-scroll">
|
||||||
|
<div class="wy-side-nav-search">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="index.html" class="icon icon-home"> R3 Corda
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="version">
|
||||||
|
latest
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div role="search">
|
||||||
|
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
|
||||||
|
<input type="text" name="q" placeholder="Search docs" />
|
||||||
|
<input type="hidden" name="check_keywords" value="yes" />
|
||||||
|
<input type="hidden" name="area" value="default" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<br>
|
||||||
|
API reference: <a href="api/kotlin/corda/index.html">Kotlin</a>/ <a href="api/javadoc/index.html">JavaDoc</a>
|
||||||
|
<br>
|
||||||
|
<a href="https://discourse.corda.net">Discourse Forums</a>
|
||||||
|
<br>
|
||||||
|
<a href="http://slack.corda.net">Slack</a>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p class="caption"><span class="caption-text">Getting started</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="inthebox.html">What’s included?</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="getting-set-up.html">Getting set up</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="getting-set-up-fault-finding.html">Troubleshooting</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="running-the-demos.html">Running the demos</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="CLI-vs-IDE.html">CLI vs IDE</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">Key concepts</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts.html">Overview</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-ecosystem.html">Corda ecosystem</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-data-model.html">Data model</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-core-types.html">Core types</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-financial-model.html">Financial model</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-flow-framework.html">Flow framework</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-consensus-notaries.html">Consensus and notaries</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-vault.html">Vault</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="key-concepts-security-model.html">Security model</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">CorDapps</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="creating-a-cordapp.html">CorDapp basics</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-cordapp.html">The example CorDapp</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">The Corda node</span></p>
|
||||||
|
<ul class="current">
|
||||||
|
<li class="toctree-l1 current"><a class="current reference internal" href="#">Shell</a><ul>
|
||||||
|
<li class="toctree-l2"><a class="reference internal" href="#getting-help">Getting help</a></li>
|
||||||
|
<li class="toctree-l2"><a class="reference internal" href="#starting-flows-and-performing-remote-method-calls">Starting flows and performing remote method calls</a></li>
|
||||||
|
<li class="toctree-l2"><a class="reference internal" href="#data-syntax">Data syntax</a></li>
|
||||||
|
<li class="toctree-l2"><a class="reference internal" href="#attachments">Attachments</a></li>
|
||||||
|
<li class="toctree-l2"><a class="reference internal" href="#extending-the-shell">Extending the shell</a></li>
|
||||||
|
<li class="toctree-l2"><a class="reference internal" href="#limitations">Limitations</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="serialization.html">Object Serialization</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="clientrpc.html">Client RPC</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="messaging.html">Networking and messaging</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="persistence.html">Persistence</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="node-administration.html">Node administration</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="corda-configuration-file.html">Node configuration</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="corda-plugins.html">The Corda plugin framework</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="node-services.html">Brief introduction to the node services</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="node-explorer.html">Node Explorer</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="permissioning.html">Network permissioning</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">Tutorials</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-contract.html">Writing a contract</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-contract-clauses.html">Writing a contract using clauses</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-test-dsl.html">Writing a contract test</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="contract-upgrade.html">Upgrading Contracts</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-integration-testing.html">Integration testing</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-clientrpc-api.html">Client RPC API tutorial</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-building-transactions.html">Building transactions</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="flow-state-machines.html">Writing flows</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="flow-testing.html">Writing flow tests</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="running-a-notary.html">Running a notary service</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="using-a-notary.html">Using a notary service</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="oracles.html">Writing oracle services</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="tutorial-attachments.html">Using attachments</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="event-scheduling.html">Event scheduling</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">Other</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="network-simulator.html">Network Simulator</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="clauses.html">Clauses</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="merkle-trees.html">Transaction tear-offs</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="json.html">JSON</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">Component library</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="contract-catalogue.html">Contract catalogue</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="contract-irs.html">Interest rate swaps</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">Appendix</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="loadtesting.html">Load testing</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="setting-up-a-corda-network.html">What is a corda network?</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="secure-coding-guidelines.html">Secure coding guidelines</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="release-process.html">Release process</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="release-notes.html">Release notes</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="changelog.html">Changelog</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="codestyle.html">Code style guide</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="building-the-docs.html">Building the documentation</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="further-notes-on-kotlin.html">Further notes on Kotlin</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="publishing-corda.html">Publishing Corda</a></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="azure-vm.html">Working with the Corda Demo on Azure Marketplace</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="caption"><span class="caption-text">Glossary</span></p>
|
||||||
|
<ul>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="glossary.html">Glossary</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||||
|
|
||||||
|
|
||||||
|
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
|
||||||
|
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||||
|
<a href="index.html">R3 Corda</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="wy-nav-content">
|
||||||
|
<div class="rst-content">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div role="navigation" aria-label="breadcrumbs navigation">
|
||||||
|
<ul class="wy-breadcrumbs">
|
||||||
|
<li><a href="index.html">Docs</a> »</li>
|
||||||
|
|
||||||
|
<li>Shell</li>
|
||||||
|
<li class="wy-breadcrumbs-aside">
|
||||||
|
|
||||||
|
|
||||||
|
<a href="_sources/shell.txt" rel="nofollow"> View page source</a>
|
||||||
|
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||||
|
<div itemprop="articleBody">
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/codesets.js"></script><div class="section" id="shell">
|
||||||
|
<h1>Shell<a class="headerlink" href="#shell" title="Permalink to this headline">¶</a></h1>
|
||||||
|
<p>The Corda shell is an embedded command line that allows an administrator to control and monitor the node.
|
||||||
|
Some of its features include:</p>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Invoking any of the RPCs the node exposes to applications.</li>
|
||||||
|
<li>Starting flows.</li>
|
||||||
|
<li>View a dashboard of threads, heap usage, VM properties.</li>
|
||||||
|
<li>Uploading and downloading zips from the attachment store.</li>
|
||||||
|
<li>Issue SQL queries to the underlying database.</li>
|
||||||
|
<li>View JMX metrics and monitoring exports.</li>
|
||||||
|
<li>UNIX style pipes for both text and objects, an <code class="docutils literal"><span class="pre">egrep</span></code> command and a command for working with columnular data.</li>
|
||||||
|
</ul>
|
||||||
|
<div class="admonition note">
|
||||||
|
<p class="first admonition-title">Note</p>
|
||||||
|
<p class="last">A future version of Corda will add SSH access to the node.</p>
|
||||||
|
</div>
|
||||||
|
<p>It is based on the popular <a class="reference external" href="http://www.crashub.org/">CRaSH</a> shell used in various other projects and supports many of the same features.</p>
|
||||||
|
<p>The shell may be disabled by passing the <code class="docutils literal"><span class="pre">--no-local-shell</span></code> flag to the node.</p>
|
||||||
|
<div class="section" id="getting-help">
|
||||||
|
<h2>Getting help<a class="headerlink" href="#getting-help" title="Permalink to this headline">¶</a></h2>
|
||||||
|
<p>You can run <code class="docutils literal"><span class="pre">help</span></code> to list the available commands.</p>
|
||||||
|
<p>The shell has a <code class="docutils literal"><span class="pre">man</span></code> command that can be used to get interactive help on many commands. You can also use the
|
||||||
|
<code class="docutils literal"><span class="pre">--help</span></code> or <code class="docutils literal"><span class="pre">-h</span></code> flags to a command to get info about what switches it supports.</p>
|
||||||
|
<p>Commands may have subcommands, in the same style as <code class="docutils literal"><span class="pre">git</span></code>. In that case running the command by itself will
|
||||||
|
list the supported subcommands.</p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="starting-flows-and-performing-remote-method-calls">
|
||||||
|
<h2>Starting flows and performing remote method calls<a class="headerlink" href="#starting-flows-and-performing-remote-method-calls" title="Permalink to this headline">¶</a></h2>
|
||||||
|
<p><strong>Flows</strong> are the way the ledger is changed. If you aren’t familiar with them, please review “<a class="reference internal" href="flow-state-machines.html"><span class="doc">Writing flows</span></a>”
|
||||||
|
first. The <code class="docutils literal"><span class="pre">flow</span> <span class="pre">list</span></code> command can be used to list the flows understood by the node and <code class="docutils literal"><span class="pre">flow</span> <span class="pre">start</span></code> can be
|
||||||
|
used to start them. The <code class="docutils literal"><span class="pre">flow</span> <span class="pre">start</span></code> command takes the class name of a flow, or <em>any unambiguous substring</em> and
|
||||||
|
then the data to be passed to the flow constructor. The unambiguous substring feature is helpful for reducing
|
||||||
|
the needed typing. If the match is ambiguous the possible matches will be printed out. If a flow has multiple
|
||||||
|
constructors then the names and types of the arguments will be used to try and determine which to use automatically.
|
||||||
|
If the match against available constructors is unclear, the reasons each available constructor failed to match
|
||||||
|
will be printed out. In the case of an ambiguous match, the first applicable will be used.</p>
|
||||||
|
<p><strong>RPCs</strong> (remote procedure calls) are commands that can be sent to the node to query it, control it and manage it.
|
||||||
|
RPCs don’t typically do anything that changes the global ledger, but they may change node-specific data in the
|
||||||
|
database. Each RPC is one method on the <code class="docutils literal"><span class="pre">CordaRPCOps</span></code> interface, and may return a stream of events that will
|
||||||
|
be shown on screen until you press Ctrl-C. You perform an RPC by using <code class="docutils literal"><span class="pre">run</span></code> followed by the name.</p>
|
||||||
|
<center><b><a href="api/kotlin/corda/net.corda.core.messaging/-corda-r-p-c-ops/index.html">Documentation of available RPCs</a></b><p></center><p>Whichever form of change is used, there is a need to provide <em>parameters</em> to either the RPC or the flow
|
||||||
|
constructor. Because parameters can be any arbitrary Java object graph, we need a convenient syntax to express
|
||||||
|
this sort of data. The shell uses a syntax called <a class="reference external" href="http://www.yaml.org/spec/1.2/spec.html">Yaml</a> to do this.</p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="data-syntax">
|
||||||
|
<h2>Data syntax<a class="headerlink" href="#data-syntax" title="Permalink to this headline">¶</a></h2>
|
||||||
|
<p>Yaml (yet another markup language) is a simple JSON-like way to describe object graphs. It has several features
|
||||||
|
that make it helpful for our use case, like a lightweight syntax and support for “bare words” which mean you can
|
||||||
|
often skip the quotes around strings. Here is an example of how this syntax is used:</p>
|
||||||
|
<p><code class="docutils literal"><span class="pre">flow</span> <span class="pre">start</span> <span class="pre">CashIssue</span> <span class="pre">amount:</span> <span class="pre">$1000,</span> <span class="pre">issueRef:</span> <span class="pre">1234,</span> <span class="pre">recipient:</span> <span class="pre">Bank</span> <span class="pre">A,</span> <span class="pre">notary:</span> <span class="pre">Notary</span> <span class="pre">Service</span></code></p>
|
||||||
|
<p>This invokes a constructor of a flow with the following prototype in the code:</p>
|
||||||
|
<div class="codeset container">
|
||||||
|
<div class="highlight-kotlin"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">CashIssueFlow</span><span class="p">(</span><span class="k">val</span> <span class="py">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p"><</span><span class="n">Currency</span><span class="p">>,</span>
|
||||||
|
<span class="k">val</span> <span class="py">issueRef</span><span class="p">:</span> <span class="n">OpaqueBytes</span><span class="p">,</span>
|
||||||
|
<span class="k">val</span> <span class="py">recipient</span><span class="p">:</span> <span class="n">Party</span><span class="p">,</span>
|
||||||
|
<span class="k">val</span> <span class="py">notary</span><span class="p">:</span> <span class="n">Party</span><span class="p">)</span> <span class="p">:</span> <span class="n">AbstractCashFlow</span><span class="p">(</span><span class="n">progressTracker</span><span class="p">)</span>
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Here, everything after <code class="docutils literal"><span class="pre">CashIssue</span></code> is specifying the arguments to the constructor of a flow. In Yaml, an object
|
||||||
|
is specified as a set of <code class="docutils literal"><span class="pre">key:</span> <span class="pre">value</span></code> pairs and in our form, we separate them by commas. There are a few things
|
||||||
|
to note about this syntax:</p>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>When a parameter is of type <code class="docutils literal"><span class="pre">Amount<Currency></span></code> you can write it as either one of the dollar symbol ($),
|
||||||
|
pound (£), euro (€) followed by the amount as a decimal, or as the value followed by the ISO currency code
|
||||||
|
e.g. “100.12 CHF”</li>
|
||||||
|
<li><code class="docutils literal"><span class="pre">OpaqueBytes</span></code> is filled with the contents of whatever is provided as a string.</li>
|
||||||
|
<li><code class="docutils literal"><span class="pre">Party</span></code> objects are looked up by name.</li>
|
||||||
|
<li>Strings do not need to be surrounded by quotes unless they contain a comma or embedded quotes. This makes it
|
||||||
|
a lot more convenient to type such strings.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Other types also have sensible mappings from strings. See <a class="reference external" href="api/kotlin/corda/net.corda.jackson/-jackson-support/index.html">the defined parsers</a> for more information.</p>
|
||||||
|
<p>Nested objects can be created using curly braces, as in <code class="docutils literal"><span class="pre">{</span> <span class="pre">a:</span> <span class="pre">1,</span> <span class="pre">b:</span> <span class="pre">2}</span></code>. This is helpful when no particular
|
||||||
|
parser is defined for the type you need, for instance, if an API requires a <code class="docutils literal"><span class="pre">Pair<String,</span> <span class="pre">Int></span></code>
|
||||||
|
which could be represented as <code class="docutils literal"><span class="pre">{</span> <span class="pre">first:</span> <span class="pre">foo,</span> <span class="pre">second:</span> <span class="pre">123</span> <span class="pre">}</span></code>.</p>
|
||||||
|
<p>The same syntax is also used to specify the parameters for RPCs, accessed via the <code class="docutils literal"><span class="pre">run</span></code> command, like this:</p>
|
||||||
|
<p><code class="docutils literal"><span class="pre">run</span> <span class="pre">getCashBalances</span></code></p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="attachments">
|
||||||
|
<h2>Attachments<a class="headerlink" href="#attachments" title="Permalink to this headline">¶</a></h2>
|
||||||
|
<p>The shell can be used to upload and download attachments from the node interactively. To learn more, see
|
||||||
|
the tutorial “<a class="reference internal" href="tutorial-attachments.html"><span class="doc">Using attachments</span></a>”.</p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="extending-the-shell">
|
||||||
|
<h2>Extending the shell<a class="headerlink" href="#extending-the-shell" title="Permalink to this headline">¶</a></h2>
|
||||||
|
<p>The shell can be extended using commands written in either Java or <a class="reference external" href="http://groovy-lang.org/">Groovy</a> (Groovy is a scripting language that
|
||||||
|
is Java compatible). Such commands have full access to the node internal APIs and thus can be used to achieve
|
||||||
|
almost anything.</p>
|
||||||
|
<p>A full tutorial on how to write such commands is out of scope for this documentation, to learn more please
|
||||||
|
refer to the <a class="reference external" href="http://www.crashub.org/">CRaSH</a> documentation. New commands can be placed in the <code class="docutils literal"><span class="pre">shell-commands</span></code> subdirectory in the
|
||||||
|
node directory. Edits to existing commands will be used automatically, but at this time commands added after the
|
||||||
|
node has started won’t be automatically detected. Commands should be named in all lower case with either a
|
||||||
|
<code class="docutils literal"><span class="pre">.java</span></code> or <code class="docutils literal"><span class="pre">.groovy</span></code> extension.</p>
|
||||||
|
<div class="admonition warning">
|
||||||
|
<p class="first admonition-title">Warning</p>
|
||||||
|
<p class="last">Commands written in Groovy ignore Java security checks, so have unrestricted access to node and JVM
|
||||||
|
internals regardless of any sandboxing that may be in place. Don’t allow untrusted users to edit files in the
|
||||||
|
shell-commands directory!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="limitations">
|
||||||
|
<h2>Limitations<a class="headerlink" href="#limitations" title="Permalink to this headline">¶</a></h2>
|
||||||
|
<p>The shell will be enhanced over time. The currently known limitations include:</p>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>You cannot use it to upload/download attachments.</li>
|
||||||
|
<li>SSH access is currently not available.</li>
|
||||||
|
<li>There is no command completion for flows or RPCs.</li>
|
||||||
|
<li>Command history is not preserved across restarts.</li>
|
||||||
|
<li>The <code class="docutils literal"><span class="pre">jdbc</span></code> command requires you to explicitly log into the database first.</li>
|
||||||
|
<li>Commands placed in the <code class="docutils literal"><span class="pre">shell-commands</span></code> directory are only noticed after the node is restarted.</li>
|
||||||
|
<li>The <code class="docutils literal"><span class="pre">jul</span></code> command advertises access to logs, but it doesn’t work with the logging framework we’re using.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
|
||||||
|
|
||||||
|
<a href="serialization.html" class="btn btn-neutral float-right" title="Object Serialization" accesskey="n">Next <span class="fa fa-arrow-circle-right"></span></a>
|
||||||
|
|
||||||
|
|
||||||
|
<a href="tutorial-cordapp.html" class="btn btn-neutral" title="The example CorDapp" accesskey="p"><span class="fa fa-arrow-circle-left"></span> Previous</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div role="contentinfo">
|
||||||
|
<p>
|
||||||
|
© Copyright 2016, R3 Limited.
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var DOCUMENTATION_OPTIONS = {
|
||||||
|
URL_ROOT:'./',
|
||||||
|
VERSION:'latest',
|
||||||
|
COLLAPSE_INDEX:false,
|
||||||
|
FILE_SUFFIX:'.html',
|
||||||
|
HAS_SOURCE: true
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/underscore.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/doctools.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/js/theme.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery(function () {
|
||||||
|
SphinxRtdTheme.StickyNav.enable();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -14,10 +14,6 @@ API changes:
|
|||||||
using strings like "1000.00 USD" when writing. You can use any format supported by ``Amount.parseCurrency``
|
using strings like "1000.00 USD" when writing. You can use any format supported by ``Amount.parseCurrency``
|
||||||
as input.
|
as input.
|
||||||
|
|
||||||
|
|
||||||
Milestone 10
|
|
||||||
------------
|
|
||||||
|
|
||||||
* Configuration:
|
* Configuration:
|
||||||
* Replace ``artemisPort`` with ``p2pPort`` in Gradle configuration
|
* Replace ``artemisPort`` with ``p2pPort`` in Gradle configuration
|
||||||
* Replace ``artemisAddress`` with ``p2pAddress`` in node configuration
|
* Replace ``artemisAddress`` with ``p2pAddress`` in node configuration
|
||||||
|
@ -75,60 +75,3 @@ node is running out of memory, you can give it more by running the node like thi
|
|||||||
The example command above would give a 1 gigabyte Java heap.
|
The example command above would give a 1 gigabyte Java heap.
|
||||||
|
|
||||||
.. note:: Unfortunately the JVM does not let you limit the total memory usage of Java program, just the heap size.
|
.. note:: Unfortunately the JVM does not let you limit the total memory usage of Java program, just the heap size.
|
||||||
|
|
||||||
Uploading and downloading attachments
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
Attachments are files that add context to and influence the behaviour of transactions. They are always identified by
|
|
||||||
hash and they are public, in that they propagate through the network to wherever they are needed.
|
|
||||||
|
|
||||||
All attachments are zip files. Thus to upload a file to the ledger you must first wrap it into a zip (or jar) file. Then
|
|
||||||
you can upload it by running this command from a UNIX terminal:
|
|
||||||
|
|
||||||
.. sourcecode:: shell
|
|
||||||
|
|
||||||
curl -F myfile=@path/to/my/file.zip http://localhost:31338/upload/attachment
|
|
||||||
|
|
||||||
The attachment will be identified by the SHA-256 hash of the contents, which you can get by doing:
|
|
||||||
|
|
||||||
.. sourcecode:: shell
|
|
||||||
|
|
||||||
shasum -a 256 file.zip
|
|
||||||
|
|
||||||
on a Mac or by using ``sha256sum`` on Linux. Alternatively, the hash will be returned to you when you upload the
|
|
||||||
attachment.
|
|
||||||
|
|
||||||
An attachment may be downloaded by fetching:
|
|
||||||
|
|
||||||
.. sourcecode:: shell
|
|
||||||
|
|
||||||
http://localhost:31338/attachments/DECD098666B9657314870E192CED0C3519C2C9D395507A238338F8D003929DE9
|
|
||||||
|
|
||||||
where DECD... is of course replaced with the hash identifier of your own attachment. Because attachments are always
|
|
||||||
containers, you can also fetch a specific file within the attachment by appending its path, like this:
|
|
||||||
|
|
||||||
.. sourcecode:: shell
|
|
||||||
|
|
||||||
http://localhost:31338/attachments/DECD098666B9657314870E192CED0C3519C2C9D395507A238338F8D003929DE9/path/within/zip.txt
|
|
||||||
|
|
||||||
Uploading interest rate fixes
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
If you would like to operate an interest rate fixing service (oracle), you can upload fix data by uploading data in
|
|
||||||
a simple text format to the ``/upload/interest-rates`` path on the web server.
|
|
||||||
|
|
||||||
The file looks like this::
|
|
||||||
|
|
||||||
# Some pretend noddy rate fixes, for the interest rate oracles.
|
|
||||||
|
|
||||||
LIBOR 2016-03-16 1M = 0.678
|
|
||||||
LIBOR 2016-03-16 2M = 0.655
|
|
||||||
EURIBOR 2016-03-15 1M = 0.123
|
|
||||||
EURIBOR 2016-03-15 2M = 0.111
|
|
||||||
|
|
||||||
The columns are:
|
|
||||||
|
|
||||||
* Name of the fix
|
|
||||||
* Date of the fix
|
|
||||||
* The tenor / time to maturity in days
|
|
||||||
* The interest rate itself
|
|
||||||
|
@ -4,14 +4,14 @@ Release notes
|
|||||||
Here are release notes for each snapshot release from M9 onwards. This includes guidance on how to upgrade code from
|
Here are release notes for each snapshot release from M9 onwards. This includes guidance on how to upgrade code from
|
||||||
the previous milestone release.
|
the previous milestone release.
|
||||||
|
|
||||||
Milestone 10
|
UNRELEASED
|
||||||
------------
|
----------
|
||||||
|
|
||||||
Important: There are configuration changes in M10 due to the split of the Artemis port into separate P2P and RPC
|
A new interactive **Corda Shell** has been added to the node. The shell lets developers and node administrators
|
||||||
ports. To upgrade, you *must*:
|
easily command the node by running flows, RPCs and SQL queries. It also provides a variety of commands to monitor
|
||||||
|
the node. The Corda Shell is based on the popular `CRaSH project <http://www.crashub.org/>`_ and new commands can
|
||||||
1. In Gradle build configurations replace any references to ``artemisPort`` with ``p2pPort``.
|
be easily added to the node by simply dropping Groovy or Java files into the node's ``shell-commands`` directory.
|
||||||
2. In node configurations replace ``artemisAddress`` with ``p2pAddress``.
|
We have many enhancements planned over time including SSH access, more commands and better tab completion.
|
||||||
|
|
||||||
Milestone 9
|
Milestone 9
|
||||||
-----------
|
-----------
|
||||||
|
@ -13,6 +13,7 @@ Some of its features include:
|
|||||||
* Invoking any of the RPCs the node exposes to applications.
|
* Invoking any of the RPCs the node exposes to applications.
|
||||||
* Starting flows.
|
* Starting flows.
|
||||||
* View a dashboard of threads, heap usage, VM properties.
|
* View a dashboard of threads, heap usage, VM properties.
|
||||||
|
* Uploading and downloading zips from the attachment store.
|
||||||
* Issue SQL queries to the underlying database.
|
* Issue SQL queries to the underlying database.
|
||||||
* View JMX metrics and monitoring exports.
|
* View JMX metrics and monitoring exports.
|
||||||
* UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data.
|
* UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data.
|
||||||
@ -101,6 +102,12 @@ The same syntax is also used to specify the parameters for RPCs, accessed via th
|
|||||||
|
|
||||||
``run getCashBalances``
|
``run getCashBalances``
|
||||||
|
|
||||||
|
Attachments
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The shell can be used to upload and download attachments from the node interactively. To learn more, see
|
||||||
|
the tutorial ":doc:`tutorial-attachments`".
|
||||||
|
|
||||||
Extending the shell
|
Extending the shell
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@ -123,7 +130,6 @@ Limitations
|
|||||||
|
|
||||||
The shell will be enhanced over time. The currently known limitations include:
|
The shell will be enhanced over time. The currently known limitations include:
|
||||||
|
|
||||||
* You cannot use it to upload/download attachments.
|
|
||||||
* SSH access is currently not available.
|
* SSH access is currently not available.
|
||||||
* There is no command completion for flows or RPCs.
|
* There is no command completion for flows or RPCs.
|
||||||
* Command history is not preserved across restarts.
|
* Command history is not preserved across restarts.
|
||||||
|
@ -3,25 +3,51 @@
|
|||||||
Using attachments
|
Using attachments
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Attachments are (typically large) Zip/Jar files referenced within a transaction, but not included in the transaction
|
Attachments are ZIP/JAR files referenced from transaction by hash, but not included in the transaction
|
||||||
itself. These files can be requested from the originating node as needed, although in many cases will be cached within
|
itself. These files are automatically requested from the node sending the transaction when needed and cached
|
||||||
nodes already. Examples include:
|
locally so they are not re-requested if encountered again. Attachments typically contain:
|
||||||
|
|
||||||
* Contract executable code
|
* Contract executable code
|
||||||
* Metadata about a transaction, such as PDF version of an invoice being settled
|
* Metadata about a transaction, such as PDF version of an invoice being settled
|
||||||
* Shared information to be permanently recorded on the ledger
|
* Shared information to be permanently recorded on the ledger
|
||||||
|
|
||||||
To add attachments the file must first be added to uploaded to the node, which returns a unique ID that can be added
|
To add attachments the file must first be added to uploaded to the node, which returns a unique ID that can be added
|
||||||
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and HTTP. For
|
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and the Corda
|
||||||
instructions on HTTP upload/download please see ":doc:`node-administration`".
|
:doc:`shell`.
|
||||||
|
|
||||||
Normally attachments on transactions are fetched automatically via the ``ResolveTransactionsFlow`` when verifying
|
|
||||||
received transactions. Attachments are needed in order to validate a transaction (they include, for example, the
|
|
||||||
contract code), so must be fetched before the validation process can run. ``ResolveTransactionsFlow`` calls
|
|
||||||
``FetchTransactionsFlow`` to perform the actual retrieval.
|
|
||||||
|
|
||||||
It is encouraged that where possible attachments are reusable data, so that nodes can meaningfully cache them.
|
It is encouraged that where possible attachments are reusable data, so that nodes can meaningfully cache them.
|
||||||
|
|
||||||
|
Uploading and downloading
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
To upload an attachment to the node, or download an attachment named by its hash, you use :doc:`clientrpc`. This
|
||||||
|
is also available for interactive use via the shell. To **upload** run:
|
||||||
|
|
||||||
|
``>>> run uploadAttachment jar: /path/to/the/file.jar``
|
||||||
|
|
||||||
|
The file is uploaded, checked and if successful the hash of the file is returned. This is how the attachment is
|
||||||
|
identified inside the node.
|
||||||
|
|
||||||
|
To download an attachment, you can do:
|
||||||
|
|
||||||
|
``>>> run openAttachment id: AB7FED7663A3F195A59A0F01091932B15C22405CB727A1518418BF53C6E6663A``
|
||||||
|
|
||||||
|
which will then ask you to provide a path to save the file to. To do the same thing programmatically, you would
|
||||||
|
can pass a simple ``InputStream`` or ``SecureHash`` to the ``uploadAttachment``/``openAttachment`` RPCs from
|
||||||
|
a JVM client.
|
||||||
|
|
||||||
|
Protocol
|
||||||
|
--------
|
||||||
|
|
||||||
|
Normally attachments on transactions are fetched automatically via the ``ResolveTransactionsFlow``. Attachments
|
||||||
|
are needed in order to validate a transaction (they include, for example, the contract code), so must be fetched
|
||||||
|
before the validation process can run. ``ResolveTransactionsFlow`` calls ``FetchTransactionsFlow`` to perform the
|
||||||
|
actual retrieval.
|
||||||
|
|
||||||
|
.. note:: Future versions of Corda may support non-critical attachments that are not used for transaction verification
|
||||||
|
and which are shared explicitly. These are useful for attaching and signing auditing data with a transaction
|
||||||
|
that isn't used as part of the contract logic.
|
||||||
|
|
||||||
Attachments demo
|
Attachments demo
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -2,16 +2,21 @@ package net.corda.node
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonFactory
|
import com.fasterxml.jackson.core.JsonFactory
|
||||||
import com.fasterxml.jackson.core.JsonGenerator
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.*
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider
|
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||||
|
import com.google.common.io.Closeables
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import net.corda.core.div
|
import net.corda.core.div
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.FlowStateMachine
|
import net.corda.core.flows.FlowStateMachine
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
import net.corda.core.rootCause
|
||||||
|
import net.corda.core.then
|
||||||
import net.corda.core.utilities.Emoji
|
import net.corda.core.utilities.Emoji
|
||||||
|
import net.corda.core.write
|
||||||
import net.corda.jackson.JacksonSupport
|
import net.corda.jackson.JacksonSupport
|
||||||
import net.corda.jackson.StringToMethodCallParser
|
import net.corda.jackson.StringToMethodCallParser
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
@ -35,16 +40,16 @@ import org.crsh.vfs.spi.file.FileMountFactory
|
|||||||
import org.crsh.vfs.spi.url.ClassPathMountFactory
|
import org.crsh.vfs.spi.url.ClassPathMountFactory
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscriber
|
import rx.Subscriber
|
||||||
import java.io.FileDescriptor
|
import java.io.*
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.PrintWriter
|
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.concurrent.Future
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -52,7 +57,6 @@ import kotlin.concurrent.thread
|
|||||||
// TODO: Add command history.
|
// TODO: Add command history.
|
||||||
// TODO: Command completion.
|
// TODO: Command completion.
|
||||||
// TODO: Find a way to inject this directly into CRaSH as a command, without needing JIT source compilation.
|
// TODO: Find a way to inject this directly into CRaSH as a command, without needing JIT source compilation.
|
||||||
// TODO: Add serialisers for InputStream so attachments can be uploaded through the shell.
|
|
||||||
// TODO: Do something sensible with commands that return a future.
|
// TODO: Do something sensible with commands that return a future.
|
||||||
// TODO: Configure default renderers, send objects down the pipeline, add commands to do json/xml/yaml outputs.
|
// TODO: Configure default renderers, send objects down the pipeline, add commands to do json/xml/yaml outputs.
|
||||||
// TODO: Add a command to view last N lines/tail/control log4j2 loggers.
|
// TODO: Add a command to view last N lines/tail/control log4j2 loggers.
|
||||||
@ -76,7 +80,7 @@ object InteractiveShell {
|
|||||||
Logger.getLogger("").level = Level.OFF // TODO: Is this really needed?
|
Logger.getLogger("").level = Level.OFF // TODO: Is this really needed?
|
||||||
|
|
||||||
val classpathDriver = ClassPathMountFactory(Thread.currentThread().contextClassLoader)
|
val classpathDriver = ClassPathMountFactory(Thread.currentThread().contextClassLoader)
|
||||||
val fileDriver = FileMountFactory(Utils.getCurrentDirectory());
|
val fileDriver = FileMountFactory(Utils.getCurrentDirectory())
|
||||||
|
|
||||||
val extraCommandsPath = (dir / "shell-commands").toAbsolutePath()
|
val extraCommandsPath = (dir / "shell-commands").toAbsolutePath()
|
||||||
Files.createDirectories(extraCommandsPath)
|
Files.createDirectories(extraCommandsPath)
|
||||||
@ -158,12 +162,10 @@ object InteractiveShell {
|
|||||||
private val yamlInputMapper: ObjectMapper by lazy {
|
private val yamlInputMapper: ObjectMapper by lazy {
|
||||||
// Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra
|
// Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra
|
||||||
// serializers.
|
// serializers.
|
||||||
JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory())
|
JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory()).apply {
|
||||||
}
|
val rpcModule = SimpleModule()
|
||||||
|
rpcModule.addDeserializer(InputStream::class.java, InputStreamDeserializer)
|
||||||
private object ObservableSerializer : JsonSerializer<Observable<*>>() {
|
registerModule(rpcModule)
|
||||||
override fun serialize(value: Observable<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
|
||||||
gen.writeString("(observable)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,8 +173,9 @@ object InteractiveShell {
|
|||||||
return JacksonSupport.createNonRpcMapper(factory).apply({
|
return JacksonSupport.createNonRpcMapper(factory).apply({
|
||||||
// Register serializers for stateful objects from libraries that are special to the RPC system and don't
|
// Register serializers for stateful objects from libraries that are special to the RPC system and don't
|
||||||
// make sense to print out to the screen. For classes we own, annotations can be used instead.
|
// make sense to print out to the screen. For classes we own, annotations can be used instead.
|
||||||
val rpcModule = SimpleModule("RPC module")
|
val rpcModule = SimpleModule()
|
||||||
rpcModule.addSerializer(Observable::class.java, ObservableSerializer)
|
rpcModule.addSerializer(Observable::class.java, ObservableSerializer)
|
||||||
|
rpcModule.addSerializer(InputStream::class.java, InputStreamSerializer)
|
||||||
registerModule(rpcModule)
|
registerModule(rpcModule)
|
||||||
|
|
||||||
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
|
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
|
||||||
@ -222,6 +225,8 @@ object InteractiveShell {
|
|||||||
} 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) }
|
||||||
|
} finally {
|
||||||
|
InputStreamDeserializer.closeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +288,54 @@ object InteractiveShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CompletableFuture<Unit>? {
|
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>): Any? {
|
||||||
|
val parser = StringToMethodCallParser(CordaRPCOps::class.java, context.attributes["mapper"] as ObjectMapper)
|
||||||
|
|
||||||
|
val cmd = input.joinToString(" ").trim({ it <= ' ' })
|
||||||
|
if (cmd.toLowerCase().startsWith("startflow")) {
|
||||||
|
// The flow command provides better support and startFlow requires special handling anyway due to
|
||||||
|
// the generic startFlow RPC interface which offers no type information with which to parse the
|
||||||
|
// string form of the command.
|
||||||
|
out.println("Please use the 'flow' command to interact with flows rather than the 'run' command.", Color.yellow)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var result: Any? = null
|
||||||
|
try {
|
||||||
|
InputStreamSerializer.invokeContext = context
|
||||||
|
val call = parser.parse(context.attributes["ops"] as CordaRPCOps, cmd)
|
||||||
|
result = call.call()
|
||||||
|
if (result != null && result !is kotlin.Unit && result !is Void) {
|
||||||
|
result = printAndFollowRPCResponse(result, out)
|
||||||
|
}
|
||||||
|
if (result is Future<*>) {
|
||||||
|
if (!result.isDone) {
|
||||||
|
out.println("Waiting for completion or Ctrl-C ... ")
|
||||||
|
out.flush()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
result = result.get()
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
Thread.currentThread().interrupt()
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
throw RuntimeException(e.rootCause)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
throw RuntimeException(e.rootCause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: StringToMethodCallParser.UnparseableCallException) {
|
||||||
|
out.println(e.message, Color.red)
|
||||||
|
out.println("Please try 'man run' to learn what syntax is acceptable")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
out.println("RPC failed: ${e.rootCause}", Color.red)
|
||||||
|
} finally {
|
||||||
|
InputStreamSerializer.invokeContext = null
|
||||||
|
InputStreamDeserializer.closeAll()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): ListenableFuture<Unit>? {
|
||||||
val printerFun = { obj: Any? -> yamlMapper.writeValueAsString(obj) }
|
val printerFun = { obj: Any? -> yamlMapper.writeValueAsString(obj) }
|
||||||
toStream.println(printerFun(response))
|
toStream.println(printerFun(response))
|
||||||
toStream.flush()
|
toStream.flush()
|
||||||
@ -291,13 +343,13 @@ object InteractiveShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class PrintingSubscriber(private val printerFun: (Any?) -> String, private val toStream: PrintWriter) : Subscriber<Any>() {
|
private class PrintingSubscriber(private val printerFun: (Any?) -> String, private val toStream: PrintWriter) : Subscriber<Any>() {
|
||||||
private var count = 0;
|
private var count = 0
|
||||||
val future = CompletableFuture<Unit>()
|
val future = SettableFuture.create<Unit>()!!
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// The future is public and can be completed by something else to indicate we don't wish to follow
|
// The future is public and can be completed by something else to indicate we don't wish to follow
|
||||||
// anymore (e.g. the user pressing Ctrl-C).
|
// anymore (e.g. the user pressing Ctrl-C).
|
||||||
future.thenAccept {
|
future then {
|
||||||
if (!isUnsubscribed)
|
if (!isUnsubscribed)
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
}
|
}
|
||||||
@ -306,7 +358,7 @@ object InteractiveShell {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
override fun onCompleted() {
|
override fun onCompleted() {
|
||||||
toStream.println("Observable has completed")
|
toStream.println("Observable has completed")
|
||||||
future.complete(Unit)
|
future.set(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@ -320,13 +372,13 @@ object InteractiveShell {
|
|||||||
override fun onError(e: Throwable) {
|
override fun onError(e: Throwable) {
|
||||||
toStream.println("Observable completed with an error")
|
toStream.println("Observable completed with an error")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
future.completeExceptionally(e)
|
future.setException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kotlin bug: USELESS_CAST warning is generated below but the IDE won't let us remove it.
|
// Kotlin bug: USELESS_CAST warning is generated below but the IDE won't let us remove it.
|
||||||
@Suppress("USELESS_CAST", "UNCHECKED_CAST")
|
@Suppress("USELESS_CAST", "UNCHECKED_CAST")
|
||||||
private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): CompletableFuture<Unit>? {
|
private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): SettableFuture<Unit>? {
|
||||||
// Match on a couple of common patterns for "important" observables. It's tough to do this in a generic
|
// Match on a couple of common patterns for "important" observables. It's tough to do this in a generic
|
||||||
// way because observables can be embedded anywhere in the object graph, and can emit other arbitrary
|
// way because observables can be embedded anywhere in the object graph, and can emit other arbitrary
|
||||||
// object graphs that contain yet more observables. So we just look for top level responses that follow
|
// object graphs that contain yet more observables. So we just look for top level responses that follow
|
||||||
@ -347,4 +399,63 @@ object InteractiveShell {
|
|||||||
(observable as Observable<Any>).subscribe(subscriber)
|
(observable as Observable<Any>).subscribe(subscriber)
|
||||||
return subscriber.future
|
return subscriber.future
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//region Extra serializers
|
||||||
|
//
|
||||||
|
// These serializers are used to enable the user to specify objects that aren't natural data containers in the shell,
|
||||||
|
// and for the shell to print things out that otherwise wouldn't be usefully printable.
|
||||||
|
|
||||||
|
private object ObservableSerializer : JsonSerializer<Observable<*>>() {
|
||||||
|
override fun serialize(value: Observable<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
gen.writeString("(observable)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A file name is deserialized to an InputStream if found.
|
||||||
|
object InputStreamDeserializer : JsonDeserializer<InputStream>() {
|
||||||
|
// Keep track of them so we can close them later.
|
||||||
|
private val streams = Collections.synchronizedSet(HashSet<InputStream>())
|
||||||
|
|
||||||
|
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): InputStream {
|
||||||
|
val stream = object : FilterInputStream(BufferedInputStream(Files.newInputStream(Paths.get(p.text)))) {
|
||||||
|
override fun close() {
|
||||||
|
super.close()
|
||||||
|
streams.remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streams += stream
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeAll() {
|
||||||
|
// Clone the set with toList() here so each closed stream can be removed from the set inside close().
|
||||||
|
streams.toList().forEach { Closeables.closeQuietly(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An InputStream found in a response triggers a request to the user to provide somewhere to save it.
|
||||||
|
private object InputStreamSerializer : JsonSerializer<InputStream>() {
|
||||||
|
var invokeContext: InvocationContext<*>? = null
|
||||||
|
|
||||||
|
override fun serialize(value: InputStream, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
try {
|
||||||
|
val toPath = invokeContext!!.readLine("Path to save stream to (enter to ignore): ", true)
|
||||||
|
if (toPath == null || toPath.isBlank()) {
|
||||||
|
gen.writeString("<not saved>")
|
||||||
|
} else {
|
||||||
|
val path = Paths.get(toPath)
|
||||||
|
path.write { value.copyTo(it) }
|
||||||
|
gen.writeString("<saved to: $path>")
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
value.close()
|
||||||
|
} catch(e: IOException) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -117,9 +117,15 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
|
|||||||
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
|
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
|
||||||
override fun importAttachment(jar: InputStream): SecureHash {
|
override fun importAttachment(jar: InputStream): SecureHash {
|
||||||
require(jar !is JarInputStream)
|
require(jar !is JarInputStream)
|
||||||
|
|
||||||
|
// Read the file into RAM, hashing it to find the ID as we go. The attachment must fit into memory.
|
||||||
|
// TODO: Switch to a two-phase insert so we can handle attachments larger than RAM.
|
||||||
|
// To do this we must pipe stream into the database without knowing its hash, which we will learn only once
|
||||||
|
// the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not
|
||||||
|
// set the hash field of the new attachment record.
|
||||||
val hs = HashingInputStream(Hashing.sha256(), jar)
|
val hs = HashingInputStream(Hashing.sha256(), jar)
|
||||||
val bytes = hs.readBytes()
|
val bytes = hs.readBytes()
|
||||||
checkIsAValidJAR(hs)
|
checkIsAValidJAR(ByteArrayInputStream(bytes))
|
||||||
val id = SecureHash.SHA256(hs.hash().asBytes())
|
val id = SecureHash.SHA256(hs.hash().asBytes())
|
||||||
|
|
||||||
val count = session.withTransaction {
|
val count = session.withTransaction {
|
||||||
@ -147,7 +153,7 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
|
|||||||
val extractTo = storePath / "$id.jar"
|
val extractTo = storePath / "$id.jar"
|
||||||
try {
|
try {
|
||||||
extractTo.createDirectory()
|
extractTo.createDirectory()
|
||||||
extractZipFile(hs, extractTo)
|
extractZipFile(ByteArrayInputStream(bytes), extractTo)
|
||||||
} catch(e: FileAlreadyExistsException) {
|
} catch(e: FileAlreadyExistsException) {
|
||||||
log.trace("Did not extract attachment jar to directory because it already exists")
|
log.trace("Did not extract attachment jar to directory because it already exists")
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
@ -164,7 +170,7 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
|
|||||||
// Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not
|
// Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not
|
||||||
// a ZIP! It'll just pretend it's an empty archive, which is kind of stupid but that's how it works.
|
// a ZIP! It'll just pretend it's an empty archive, which is kind of stupid but that's how it works.
|
||||||
// So we have to check to ensure we found at least one item.
|
// So we have to check to ensure we found at least one item.
|
||||||
val jar = JarInputStream(stream)
|
val jar = JarInputStream(stream, true)
|
||||||
var count = 0
|
var count = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
val cursor = jar.nextJarEntry ?: break
|
val cursor = jar.nextJarEntry ?: break
|
||||||
|
@ -6,6 +6,7 @@ import org.crsh.cli.*;
|
|||||||
import org.crsh.command.*;
|
import org.crsh.command.*;
|
||||||
import org.crsh.text.*;
|
import org.crsh.text.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
@ -37,46 +38,7 @@ public class run extends InteractiveShellCommand {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String cmd = String.join(" ", command).trim();
|
return InteractiveShell.runRPCFromString(command, out, context);
|
||||||
if (cmd.toLowerCase().startsWith("startflow")) {
|
|
||||||
// The flow command provides better support and startFlow requires special handling anyway due to
|
|
||||||
// the generic startFlow RPC interface which offers no type information with which to parse the
|
|
||||||
// string form of the command.
|
|
||||||
out.println("Please use the 'flow' command to interact with flows rather than the 'run' command.", Color.yellow);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object result = null;
|
|
||||||
try {
|
|
||||||
StringToMethodCallParser.ParsedMethodCall call = parser.parse(ops(), cmd);
|
|
||||||
result = call.call();
|
|
||||||
result = processResult(result);
|
|
||||||
} catch (StringToMethodCallParser.UnparseableCallException e) {
|
|
||||||
out.println(e.getMessage(), Color.red);
|
|
||||||
out.println("Please try 'man run' to learn what syntax is acceptable", Color.red);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object processResult(Object result) {
|
|
||||||
if (result != null && !(result instanceof kotlin.Unit) && !(result instanceof Void)) {
|
|
||||||
result = printAndFollowRPCResponse(result, out);
|
|
||||||
}
|
|
||||||
if (result instanceof Future) {
|
|
||||||
Future future = (Future) result;
|
|
||||||
if (!future.isDone()) {
|
|
||||||
out.println("Waiting for completion or Ctrl-C ... ");
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
result = future.get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {
|
private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user