Shell: add support for uploading and downloading attachments.

This commit is contained in:
Mike Hearn 2017-03-21 12:13:37 +01:00
parent 347224c900
commit ac90fe724e
10 changed files with 630 additions and 148 deletions

View File

@ -18,7 +18,11 @@ import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.security.SignatureException
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
@ -134,9 +138,18 @@ class ResolveTransactionsFlowTest {
@Test
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
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 p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)

419
docs/build/html/shell.html vendored Normal file
View 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 &mdash; 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&#8217;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> &raquo;</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&#8217;t familiar with them, please review &#8220;<a class="reference internal" href="flow-state-machines.html"><span class="doc">Writing flows</span></a>&#8221;
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&#8217;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 &#8220;bare words&#8221; 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">&lt;</span><span class="n">Currency</span><span class="p">&gt;,</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&lt;Currency&gt;</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. &#8220;100.12 CHF&#8221;</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&lt;String,</span> <span class="pre">Int&gt;</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 &#8220;<a class="reference internal" href="tutorial-attachments.html"><span class="doc">Using attachments</span></a>&#8221;.</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&#8217;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&#8217;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&#8217;t work with the logging framework we&#8217;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>
&copy; 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>

View File

@ -14,10 +14,6 @@ API changes:
using strings like "1000.00 USD" when writing. You can use any format supported by ``Amount.parseCurrency``
as input.
Milestone 10
------------
* Configuration:
* Replace ``artemisPort`` with ``p2pPort`` in Gradle configuration
* Replace ``artemisAddress`` with ``p2pAddress`` in node configuration

View File

@ -74,61 +74,4 @@ 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.
.. 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
.. note:: Unfortunately the JVM does not let you limit the total memory usage of Java program, just the heap size.

View File

@ -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
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
ports. To upgrade, you *must*:
1. In Gradle build configurations replace any references to ``artemisPort`` with ``p2pPort``.
2. In node configurations replace ``artemisAddress`` with ``p2pAddress``.
A new interactive **Corda Shell** has been added to the node. The shell lets developers and node administrators
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
be easily added to the node by simply dropping Groovy or Java files into the node's ``shell-commands`` directory.
We have many enhancements planned over time including SSH access, more commands and better tab completion.
Milestone 9
-----------

View File

@ -13,6 +13,7 @@ Some of its features include:
* Invoking any of the RPCs the node exposes to applications.
* Starting flows.
* View a dashboard of threads, heap usage, VM properties.
* Uploading and downloading zips from the attachment store.
* Issue SQL queries to the underlying database.
* 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.
@ -101,6 +102,12 @@ The same syntax is also used to specify the parameters for RPCs, accessed via th
``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
-------------------
@ -123,7 +130,6 @@ Limitations
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.
* There is no command completion for flows or RPCs.
* Command history is not preserved across restarts.

View File

@ -3,25 +3,51 @@
Using attachments
=================
Attachments are (typically large) Zip/Jar files referenced within a transaction, 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
nodes already. Examples include:
Attachments are ZIP/JAR files referenced from transaction by hash, but not included in the transaction
itself. These files are automatically requested from the node sending the transaction when needed and cached
locally so they are not re-requested if encountered again. Attachments typically contain:
* Contract executable code
* Metadata about a transaction, such as PDF version of an invoice being settled
* 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
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and HTTP. For
instructions on HTTP upload/download please see ":doc:`node-administration`".
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.
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and the Corda
:doc:`shell`.
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
----------------

View File

@ -2,16 +2,21 @@ package net.corda.node
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.module.SimpleModule
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.flows.FlowLogic
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.write
import net.corda.jackson.JacksonSupport
import net.corda.jackson.StringToMethodCallParser
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 rx.Observable
import rx.Subscriber
import java.io.FileDescriptor
import java.io.FileInputStream
import java.io.PrintWriter
import java.io.*
import java.lang.reflect.Constructor
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutionException
import java.util.concurrent.Future
import java.util.logging.Level
import java.util.logging.Logger
import kotlin.concurrent.thread
@ -52,7 +57,6 @@ import kotlin.concurrent.thread
// TODO: Add command history.
// TODO: Command completion.
// 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: 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.
@ -76,7 +80,7 @@ object InteractiveShell {
Logger.getLogger("").level = Level.OFF // TODO: Is this really needed?
val classpathDriver = ClassPathMountFactory(Thread.currentThread().contextClassLoader)
val fileDriver = FileMountFactory(Utils.getCurrentDirectory());
val fileDriver = FileMountFactory(Utils.getCurrentDirectory())
val extraCommandsPath = (dir / "shell-commands").toAbsolutePath()
Files.createDirectories(extraCommandsPath)
@ -158,12 +162,10 @@ object InteractiveShell {
private val yamlInputMapper: ObjectMapper by lazy {
// Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra
// serializers.
JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory())
}
private object ObservableSerializer : JsonSerializer<Observable<*>>() {
override fun serialize(value: Observable<*>, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString("(observable)")
JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory()).apply {
val rpcModule = SimpleModule()
rpcModule.addDeserializer(InputStream::class.java, InputStreamDeserializer)
registerModule(rpcModule)
}
}
@ -171,8 +173,9 @@ object InteractiveShell {
return JacksonSupport.createNonRpcMapper(factory).apply({
// 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.
val rpcModule = SimpleModule("RPC module")
val rpcModule = SimpleModule()
rpcModule.addSerializer(Observable::class.java, ObservableSerializer)
rpcModule.addSerializer(InputStream::class.java, InputStreamSerializer)
registerModule(rpcModule)
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
@ -222,6 +225,8 @@ object InteractiveShell {
} catch(e: NoApplicableConstructor) {
output.println("No matching constructor found:", Color.red)
e.errors.forEach { output.println("- $it", Color.red) }
} finally {
InputStreamDeserializer.closeAll()
}
}
@ -283,7 +288,54 @@ object InteractiveShell {
}
@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) }
toStream.println(printerFun(response))
toStream.flush()
@ -291,13 +343,13 @@ object InteractiveShell {
}
private class PrintingSubscriber(private val printerFun: (Any?) -> String, private val toStream: PrintWriter) : Subscriber<Any>() {
private var count = 0;
val future = CompletableFuture<Unit>()
private var count = 0
val future = SettableFuture.create<Unit>()!!
init {
// 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).
future.thenAccept {
future then {
if (!isUnsubscribed)
unsubscribe()
}
@ -306,7 +358,7 @@ object InteractiveShell {
@Synchronized
override fun onCompleted() {
toStream.println("Observable has completed")
future.complete(Unit)
future.set(Unit)
}
@Synchronized
@ -320,13 +372,13 @@ object InteractiveShell {
override fun onError(e: Throwable) {
toStream.println("Observable completed with an error")
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.
@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
// 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
@ -347,4 +399,63 @@ object InteractiveShell {
(observable as Observable<Any>).subscribe(subscriber)
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
}

View File

@ -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.
override fun importAttachment(jar: InputStream): SecureHash {
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 bytes = hs.readBytes()
checkIsAValidJAR(hs)
checkIsAValidJAR(ByteArrayInputStream(bytes))
val id = SecureHash.SHA256(hs.hash().asBytes())
val count = session.withTransaction {
@ -147,7 +153,7 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
val extractTo = storePath / "$id.jar"
try {
extractTo.createDirectory()
extractZipFile(hs, extractTo)
extractZipFile(ByteArrayInputStream(bytes), extractTo)
} catch(e: FileAlreadyExistsException) {
log.trace("Did not extract attachment jar to directory because it already exists")
} 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
// 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.
val jar = JarInputStream(stream)
val jar = JarInputStream(stream, true)
var count = 0
while (true) {
val cursor = jar.nextJarEntry ?: break

View File

@ -6,6 +6,7 @@ import org.crsh.cli.*;
import org.crsh.command.*;
import org.crsh.text.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
@ -37,46 +38,7 @@ public class run extends InteractiveShellCommand {
return null;
}
String cmd = String.join(" ", command).trim();
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;
return InteractiveShell.runRPCFromString(command, out, context);
}
private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {