mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
EG-1404 - Retired the rst doc source structure under /docs and update… (#6147)
* Retired the rst doc source structure under /docs and updated the /docs/README.md *Rollback of /example-code and /whitepaper dirs back under /docs dir until new code example process is in place
This commit is contained in:
parent
0beee5beff
commit
73d5fc4db6
197
docs/Makefile
197
docs/Makefile
@ -1,197 +0,0 @@
|
|||||||
# Makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = sphinx-build
|
|
||||||
PAPER =
|
|
||||||
BUILDDIR = build
|
|
||||||
|
|
||||||
# User-friendly check for sphinx-build
|
|
||||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
|
||||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Internal variables.
|
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
|
||||||
HTMLSPHINXOPTS = $(ALLSPHINXOPTS) -t htmlmode
|
|
||||||
PDFSPHINXOPTS = $(ALLSPHINXOPTS) -t pdfmode
|
|
||||||
# the i18n builder cannot share the environment and doctrees with the others
|
|
||||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
|
||||||
|
|
||||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " applehelp to make an Apple Help Book"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " texinfo to make Texinfo files"
|
|
||||||
@echo " info to make Texinfo files and run them through makeinfo"
|
|
||||||
@echo " gettext to make PO message catalogs"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " xml to make Docutils-native XML files"
|
|
||||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(BUILDDIR)/*
|
|
||||||
|
|
||||||
html:
|
|
||||||
$(SPHINXBUILD) -b html $(HTMLSPHINXOPTS) $(BUILDDIR)/html
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
dirhtml:
|
|
||||||
$(SPHINXBUILD) -b dirhtml $(HTMLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
singlehtml:
|
|
||||||
$(SPHINXBUILD) -b singlehtml $(HTMLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
pickle:
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files."
|
|
||||||
|
|
||||||
json:
|
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the JSON files."
|
|
||||||
|
|
||||||
htmlhelp:
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(HTMLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
|
||||||
|
|
||||||
qthelp:
|
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Playground.qhcp"
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Playground.qhc"
|
|
||||||
|
|
||||||
applehelp:
|
|
||||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
|
||||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
|
||||||
"~/Library/Documentation/Help or install it in your application" \
|
|
||||||
"bundle."
|
|
||||||
|
|
||||||
devhelp:
|
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished."
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Playground"
|
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Playground"
|
|
||||||
@echo "# devhelp"
|
|
||||||
|
|
||||||
epub:
|
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
|
||||||
|
|
||||||
latex:
|
|
||||||
$(SPHINXBUILD) -b latex $(PDFSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
|
||||||
|
|
||||||
latexpdf:
|
|
||||||
$(SPHINXBUILD) -b latex $(PDFSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
latexpdfja:
|
|
||||||
$(SPHINXBUILD) -b latex $(PDFSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
text:
|
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
|
||||||
|
|
||||||
man:
|
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
|
||||||
|
|
||||||
texinfo:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
|
||||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
|
||||||
"(use \`make info' here to do that automatically)."
|
|
||||||
|
|
||||||
info:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
|
||||||
make -C $(BUILDDIR)/texinfo info
|
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
|
||||||
|
|
||||||
gettext:
|
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
|
||||||
|
|
||||||
changes:
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
|
||||||
|
|
||||||
linkcheck:
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
doctest:
|
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
|
||||||
@echo "Testing of coverage in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/coverage/python.txt."
|
|
||||||
|
|
||||||
xml:
|
|
||||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
|
||||||
|
|
||||||
pseudoxml:
|
|
||||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
|
||||||
|
|
||||||
pdf:
|
|
||||||
$(SPHINXBUILD) -b pdf $(PDFSPHINXOPTS) $(BUILDDIR)/pdf
|
|
@ -1,41 +1,23 @@
|
|||||||
# Corda Documentation Build
|
# Docs
|
||||||
|
|
||||||
This Readme describes how to build the Corda documentation for the current version. The output html files will be written to the `corda\docs\build\html` directory.
|
## Released documentation
|
||||||
|
|
||||||
## Prerequisites / First time build
|
All released Corda documentation has now been moved to a standalone public documentation repository where the doc source can be found:
|
||||||
|
|
||||||
Before you begin, you need to:
|
[corda/corda-docs](https://github.com/corda/corda-docs)
|
||||||
1. Install Docker.
|
|
||||||
1. Ensure that Docker is running.
|
|
||||||
1. Select **Expose daemon on tcp://localhost:2375 without TLS** in the Docker Settings (which you can open from the **System Tray** by right-clicking the **Docker symbol** and then selecting **Settings**)
|
|
||||||
|
|
||||||
## Build process
|
See the [readme](https://github.com/corda/corda-docs/blob/master/README.md) and [usage docs](https://github.com/corda/corda-docs/tree/master/usage-docs) pages for instructions on how to use the new repo and build the docs locally.
|
||||||
1. Open a cmd dialogue.
|
|
||||||
1. Navigate to the root location (this is the `\corda` directory)
|
|
||||||
1. Run the documentation build (`gradlew makeDocs` or `./gradlew makeDocs`)
|
|
||||||
|
|
||||||
**Windows users:** *If this task fails because Docker can't find make-docsite.sh, go to Settings > Shared Drives in the Docker system tray
|
You can contribute to the docs source as before via fork & PR. We now use `markdown` to write/edit (instead of `rst`) and `Hugo` to build (instead of `Sphinx`).
|
||||||
agent, make sure the relevant drive is shared, and click 'Reset credentials'.*
|
|
||||||
|
|
||||||
# RST style guide
|
The published documentation is available at https://docs.corda.net.
|
||||||
|
|
||||||
The Corda documentation is described using the ReStructured Text (RST) markup language. For details of the syntax, see [this](http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html).
|
## Documentation for future releases
|
||||||
|
|
||||||
# Version placeholders
|
R3's technical writing team, R3 engineering, and other R3 teams use a separate, private docs repo for working on draft documentation content targeting future releases:
|
||||||
|
|
||||||
We currently support the following placeholders; they get substituted with the correct value at build time:
|
[corda/corda-docs-develop](https://github.com/corda/corda-docs-develop)
|
||||||
|
|
||||||
```groovy
|
These docs are published as part of each quarterly release of Corda. At that point their doc source becomes available and open for contributions in the [public docs repo](https://github.com/corda/corda-docs).
|
||||||
"|corda_version|"
|
|
||||||
"|corda_version_lower|"
|
|
||||||
"|java_version|"
|
|
||||||
"|kotlin_version|"
|
|
||||||
"|gradle_plugins_version|"
|
|
||||||
"|quasar_version|"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you put one of these in an rst file anywhere (including in a code tag), it will be substituted with the value from `constants.properties`
|
The new documentation process is described in the technical writing team's space on [R3's internal confluence wiki](https://r3-cev.atlassian.net/wiki/spaces/EN/pages/1701249087/Technical+Writing).
|
||||||
(which is in the root of the project) at build time. `corda_version_lower` returns the current Corda version in lowercase which is useful
|
|
||||||
for case sensitive artifacts such as docker images.
|
|
||||||
|
|
||||||
The code for this can be found near the top of the conf.py file in the `docs/source` directory.
|
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
|
||||||
|
|
||||||
import java.nio.file.Files
|
|
||||||
|
|
||||||
apply plugin: 'org.jetbrains.dokka'
|
|
||||||
apply plugin: 'kotlin'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile rootProject
|
|
||||||
}
|
|
||||||
|
|
||||||
def internalPackagePrefixes(sourceDirs) {
|
|
||||||
def prefixes = []
|
|
||||||
// Kotlin allows packages to deviate from the directory structure, but let's assume they don't:
|
|
||||||
sourceDirs.collect { sourceDir ->
|
|
||||||
sourceDir.traverse(type: groovy.io.FileType.DIRECTORIES) {
|
|
||||||
if (it.name == 'internal') {
|
|
||||||
prefixes.add sourceDir.toPath().relativize(it.toPath()).toString().replace(File.separator, '.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prefixes
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
|
||||||
// TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API
|
|
||||||
dokkaSourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/workflows/src/main/kotlin', '../finance/contracts/src/main/kotlin', '../client/jackson/src/main/kotlin',
|
|
||||||
'../testing/test-utils/src/main/kotlin', '../testing/node-driver/src/main/kotlin')
|
|
||||||
internalPackagePrefixes = internalPackagePrefixes(dokkaSourceDirs)
|
|
||||||
}
|
|
||||||
|
|
||||||
dokka {
|
|
||||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
|
||||||
outputFormat = "javadoc"
|
|
||||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
|
|
||||||
}
|
|
||||||
|
|
||||||
[dokka, dokkaJavadoc].collect {
|
|
||||||
it.configure {
|
|
||||||
moduleName = 'corda'
|
|
||||||
processConfigurations = ['compile']
|
|
||||||
sourceDirs = dokkaSourceDirs
|
|
||||||
includes = ['packages.md']
|
|
||||||
jdkVersion = 8
|
|
||||||
externalDocumentationLink {
|
|
||||||
url = new URL("http://fasterxml.github.io/jackson-core/javadoc/2.9/")
|
|
||||||
}
|
|
||||||
externalDocumentationLink {
|
|
||||||
url = new URL("https://docs.oracle.com/javafx/2/api/")
|
|
||||||
}
|
|
||||||
externalDocumentationLink {
|
|
||||||
url = new URL("http://www.bouncycastle.org/docs/docs1.5on/")
|
|
||||||
}
|
|
||||||
internalPackagePrefixes.collect { packagePrefix ->
|
|
||||||
packageOptions {
|
|
||||||
prefix = packagePrefix
|
|
||||||
suppress = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task buildDocs(dependsOn: ['apidocs', 'makeDocs'])
|
|
||||||
task apidocs(dependsOn: ['dokka', 'dokkaJavadoc'])
|
|
||||||
|
|
||||||
task makeDocs(type: Exec) {
|
|
||||||
// 2 volumes are mounted:
|
|
||||||
// - the docs project to /opt/docs_builder, where docs building is executed
|
|
||||||
// - the rest of the projects in /opt, so that code references to other projects are valid
|
|
||||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
|
||||||
commandLine "docker", "run", "--rm", "-v", "${project.projectDir}:/opt/docs_builder", "-v", "${project.projectDir}/..:/opt", "corda/docs-builder:latest", "bash", "-c", "make-docsite.sh"
|
|
||||||
} else {
|
|
||||||
commandLine "bash", "-c", "docker run --rm --user \$(id -u):\$(id -g) -v ${project.projectDir}:/opt/docs_builder -v ${project.projectDir}/..:/opt corda/docs-builder:latest bash -c make-docsite.sh"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apidocs.shouldRunAfter makeDocs
|
|
@ -1,11 +0,0 @@
|
|||||||
FROM python:2-stretch
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get --no-install-recommends install -y texlive preview-latex-style texlive-generic-extra texlive-latex-extra latexmk dos2unix \
|
|
||||||
&& apt-get -y clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
ENV PATH="/opt/docs_builder:${PATH}"
|
|
||||||
WORKDIR /opt/docs_builder
|
|
||||||
COPY requirements.txt requirements.txt
|
|
||||||
RUN pip install -r requirements.txt
|
|
1
docs/docs_builder/lexer-fix/.gitignore
vendored
1
docs/docs_builder/lexer-fix/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
Pygments*.whl
|
|
@ -1,13 +0,0 @@
|
|||||||
FROM python:2-stretch
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get --no-install-recommends install -y texlive preview-latex-style texlive-generic-extra texlive-latex-extra latexmk dos2unix \
|
|
||||||
&& apt-get -y clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
ENV PATH="/opt/docs_builder:${PATH}"
|
|
||||||
WORKDIR /opt/docs_builder
|
|
||||||
COPY requirements.txt requirements.txt
|
|
||||||
COPY docs_builder/lexer-fix/Pygments*.whl .
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
RUN pip install Pygments*.whl --force-reinstall
|
|
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
|||||||
# Pygments lexer
|
|
||||||
|
|
||||||
We were getting a lot of warnings in the docs build, and people were unable to see the real warnings due to this. So we're on a mission
|
|
||||||
to sort all of these out.
|
|
||||||
|
|
||||||
A lot of the errors were because the kotlin lexer in Pygments (the syntax highlighter that sphinx uses) didn't cope with a lot of the
|
|
||||||
kotlin syntax that we use.
|
|
||||||
|
|
||||||
We have fixes for the kotlin lexer that we are trying to get checked into Pygments, but while this is taking place we need to maintain a
|
|
||||||
slightly hacked corda/docs-build docker image in which to build the docs.
|
|
||||||
|
|
||||||
## Some notes on building and testing
|
|
||||||
|
|
||||||
The sphinx/pygments brigade have delightfully decided that mercurial is a good idea. So broadly speaking, to build/test a fix:
|
|
||||||
|
|
||||||
* checkout pygments from [here](https://bitbucket.org/birkenfeld/pygments-main/overview)
|
|
||||||
* copy the two python files in (might be worth diffing - they're based on 2.3.1 - nb the kotlin test is entirely new)
|
|
||||||
* build pygments whl file
|
|
||||||
|
|
||||||
```
|
|
||||||
cd /path/to/pygments/
|
|
||||||
python setup.py install
|
|
||||||
pip install wheel
|
|
||||||
wheel convert dist/Pygments-2.3.1.dev20190 # obviously use your version
|
|
||||||
cp Pygments-2.3.1.dev20190401-py27-none-any.whl /path/to/corda/docs/source/docs_builder/lexer-fix
|
|
||||||
```
|
|
||||||
* build the latest docker build (see docs readme)
|
|
||||||
|
|
||||||
```
|
|
||||||
cd docs
|
|
||||||
docker build -t corda/docs-builder:latest -f docs_builder/lexer-fix/Dockerfile .
|
|
||||||
```
|
|
||||||
|
|
||||||
* push the new image up to docker hub (nb you can also test by going to /opt/docs and running `./make-docsite.sh`)
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Basic JavaLexer Test
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
:copyright: Copyright 2006-2017 by the Pygments team, see AUTHORS.
|
|
||||||
:license: BSD, see LICENSE for details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from pygments.token import Text, Name, Operator, Keyword, Number, Punctuation, String
|
|
||||||
from pygments.lexers import KotlinLexer
|
|
||||||
|
|
||||||
class KotlinTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.lexer = KotlinLexer()
|
|
||||||
self.maxDiff = None
|
|
||||||
|
|
||||||
def testCanCopeWithBackTickNamesInFunctions(self):
|
|
||||||
fragment = u'fun `wo bble`'
|
|
||||||
tokens = [
|
|
||||||
(Keyword, u'fun'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name.Function, u'`wo bble`'),
|
|
||||||
(Text, u'\n')
|
|
||||||
]
|
|
||||||
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
|
||||||
|
|
||||||
def testCanCopeWithCommasAndDashesInBackTickNames(self):
|
|
||||||
fragment = u'fun `wo,-bble`'
|
|
||||||
tokens = [
|
|
||||||
(Keyword, u'fun'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name.Function, u'`wo,-bble`'),
|
|
||||||
(Text, u'\n')
|
|
||||||
]
|
|
||||||
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
|
||||||
|
|
||||||
def testCanCopeWithDestructuring(self):
|
|
||||||
fragment = u'val (a, b) = '
|
|
||||||
tokens = [
|
|
||||||
(Keyword, u'val'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Punctuation, u'('),
|
|
||||||
(Name.Property, u'a'),
|
|
||||||
(Punctuation, u','),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name.Property, u'b'),
|
|
||||||
(Punctuation, u')'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Punctuation, u'='),
|
|
||||||
(Text, u' '),
|
|
||||||
(Text, u'\n')
|
|
||||||
]
|
|
||||||
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
|
||||||
|
|
||||||
def testCanCopeGenericsInDestructuring(self):
|
|
||||||
fragment = u'val (a: List<Something>, b: Set<Wobble>) ='
|
|
||||||
tokens = [
|
|
||||||
(Keyword, u'val'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Punctuation, u'('),
|
|
||||||
(Name.Property, u'a'),
|
|
||||||
(Punctuation, u':'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name.Property, u'List'),
|
|
||||||
(Punctuation, u'<'),
|
|
||||||
(Name, u'Something'),
|
|
||||||
(Punctuation, u'>'),
|
|
||||||
(Punctuation, u','),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name.Property, u'b'),
|
|
||||||
(Punctuation, u':'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name.Property, u'Set'),
|
|
||||||
(Punctuation, u'<'),
|
|
||||||
(Name, u'Wobble'),
|
|
||||||
(Punctuation, u'>'),
|
|
||||||
(Punctuation, u')'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Punctuation, u'='),
|
|
||||||
(Text, u'\n')
|
|
||||||
]
|
|
||||||
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
|
||||||
|
|
||||||
def testCanCopeWithGenerics(self):
|
|
||||||
fragment = u'inline fun <reified T : ContractState> VaultService.queryBy(): Vault.Page<T> {'
|
|
||||||
tokens = [
|
|
||||||
(Keyword, u'inline fun'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Punctuation, u'<'),
|
|
||||||
(Keyword, u'reified'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name, u'T'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Punctuation, u':'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name, u'ContractState'),
|
|
||||||
(Punctuation, u'>'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name.Class, u'VaultService'),
|
|
||||||
(Punctuation, u'.'),
|
|
||||||
(Name.Function, u'queryBy'),
|
|
||||||
(Punctuation, u'('),
|
|
||||||
(Punctuation, u')'),
|
|
||||||
(Punctuation, u':'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Name, u'Vault'),
|
|
||||||
(Punctuation, u'.'),
|
|
||||||
(Name, u'Page'),
|
|
||||||
(Punctuation, u'<'),
|
|
||||||
(Name, u'T'),
|
|
||||||
(Punctuation, u'>'),
|
|
||||||
(Text, u' '),
|
|
||||||
(Punctuation, u'{'),
|
|
||||||
(Text, u'\n')
|
|
||||||
]
|
|
||||||
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
|
||||||
|
|
||||||
def testShouldCopeWithMultilineComments(self):
|
|
||||||
fragment = u'"""\nthis\nis\na\ncomment"""'
|
|
||||||
tokens = [
|
|
||||||
(String, u'"""\nthis\nis\na\ncomment"""'),
|
|
||||||
(Text, u'\n')
|
|
||||||
]
|
|
||||||
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,37 +0,0 @@
|
|||||||
import re
|
|
||||||
from docutils.parsers.rst import directives
|
|
||||||
from sphinx.directives.other import TocTree
|
|
||||||
|
|
||||||
def setup(app):
|
|
||||||
app.add_directive('conditional-toctree', ConditionalTocTree)
|
|
||||||
ConditionalTocTree.defined_tags = app.tags.tags.keys()
|
|
||||||
return {'version': '1.0.0'}
|
|
||||||
|
|
||||||
def tag(argument):
|
|
||||||
return directives.choice(argument, ('htmlmode', 'pdfmode'))
|
|
||||||
|
|
||||||
class ConditionalTocTree(TocTree):
|
|
||||||
|
|
||||||
defined_tags = []
|
|
||||||
has_content = True
|
|
||||||
required_arguments = 0
|
|
||||||
optional_arguments = 0
|
|
||||||
final_argument_whitespace = False
|
|
||||||
option_spec = {
|
|
||||||
'maxdepth': int,
|
|
||||||
'name': directives.unchanged,
|
|
||||||
'caption': directives.unchanged_required,
|
|
||||||
'glob': directives.flag,
|
|
||||||
'hidden': directives.flag,
|
|
||||||
'includehidden': directives.flag,
|
|
||||||
'titlesonly': directives.flag,
|
|
||||||
'reversed': directives.flag,
|
|
||||||
'if_tag': tag,
|
|
||||||
}
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if_tag = self.options.get('if_tag')
|
|
||||||
if if_tag in self.defined_tags:
|
|
||||||
return TocTree.run(self)
|
|
||||||
else:
|
|
||||||
return []
|
|
@ -1,9 +0,0 @@
|
|||||||
<!-- HTML file for redirecting to the more nested docs file solely for convenience. -->
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="refresh" content="0; url=./build/html/index.html" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,36 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# The purpose of this file is to install the requirements for the docsite
|
|
||||||
# You can call it manually if running make manually, otherwise gradle will run it for you
|
|
||||||
|
|
||||||
set -xeo pipefail
|
|
||||||
|
|
||||||
# Install the virtualenv
|
|
||||||
if [ ! -d "virtualenv" ]
|
|
||||||
then
|
|
||||||
# If the canonical working directory contains whitespace, virtualenv installs broken scripts.
|
|
||||||
# But if we pass in an absolute path that uses symlinks to avoid whitespace, that fixes the problem.
|
|
||||||
# If you run this script manually (not via gradle) from such a path alias, it's available in PWD:
|
|
||||||
absolutevirtualenv="$PWD/virtualenv"
|
|
||||||
# Check if python2.7 is installed explicitly otherwise fall back to the default python
|
|
||||||
if type "python2.7" > /dev/null; then
|
|
||||||
virtualenv -p python2.7 "$absolutevirtualenv"
|
|
||||||
else
|
|
||||||
virtualenv "$absolutevirtualenv"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Activate the virtualenv
|
|
||||||
if [ -d "virtualenv/bin" ]
|
|
||||||
then
|
|
||||||
# it's a Unix system
|
|
||||||
source virtualenv/bin/activate
|
|
||||||
else
|
|
||||||
source virtualenv/Scripts/activate
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install PIP requirements
|
|
||||||
if [ ! -d "virtualenv/lib/python2.7/site-packages/sphinx" ]
|
|
||||||
then
|
|
||||||
echo "Installing pip dependencies ... "
|
|
||||||
pip install -r requirements.txt
|
|
||||||
fi
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
echo "Generating PDF document ..."
|
|
||||||
make latexpdf LATEXMKOPTS="-quiet"
|
|
||||||
|
|
||||||
echo "Generating HTML pages ..."
|
|
||||||
make html
|
|
||||||
|
|
||||||
echo "Moving PDF file from $(eval echo $PWD/build/latex/corda-developer-site.pdf) to $(eval echo $PWD/build/html/_static/corda-developer-site.pdf)"
|
|
||||||
mv $PWD/build/latex/corda-developer-site.pdf $PWD/build/html/_static/corda-developer-site.pdf
|
|
191
docs/packages.md
191
docs/packages.md
@ -1,191 +0,0 @@
|
|||||||
# Package net.corda.client.jackson
|
|
||||||
|
|
||||||
Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
|
|
||||||
the java.time API, some core types, and Kotlin data classes.
|
|
||||||
|
|
||||||
# Package net.corda.client.rpc
|
|
||||||
|
|
||||||
RPC client interface to Corda, for use both by user-facing client and integration with external systems.
|
|
||||||
|
|
||||||
# Package net.corda.client.rpc.internal
|
|
||||||
|
|
||||||
Internal, do not use. These APIs and implementations which are currently being revised and are subject to future change.
|
|
||||||
|
|
||||||
# Package net.corda.core
|
|
||||||
|
|
||||||
Exception types and some utilities for working with observables and futures.
|
|
||||||
|
|
||||||
# Package net.corda.core.concurrent
|
|
||||||
|
|
||||||
Provides a simplified [java.util.concurrent.Future] class that allows registration of a callback to execute when the future
|
|
||||||
is complete.
|
|
||||||
|
|
||||||
# Package net.corda.core.contracts
|
|
||||||
|
|
||||||
This package contains the base data types for smarts contracts implemented in Corda. To implement a new contract start
|
|
||||||
with [Contract], or see the examples in `net.corda.finance.contracts`.
|
|
||||||
|
|
||||||
Corda smart contracts are a combination of state held on the distributed ledger, and verification logic which defines
|
|
||||||
which transformations of state are valid.
|
|
||||||
|
|
||||||
# Package net.corda.core.cordapp
|
|
||||||
|
|
||||||
This package contains the interface to CorDapps from within a node. A CorDapp can access it's own context by using
|
|
||||||
the CordappProvider.getAppContext() class. These classes are not intended to be constructed manually and no interface
|
|
||||||
to do this will be provided.
|
|
||||||
|
|
||||||
# Package net.corda.core.crypto
|
|
||||||
|
|
||||||
Cryptography data and utility classes used for signing, verifying, key management and data integrity checks.
|
|
||||||
|
|
||||||
# Package net.corda.core.flows
|
|
||||||
|
|
||||||
Base data types and abstract classes for implementing Corda flows. To implement a new flow start with [FlowLogic], or
|
|
||||||
see [CollectSignaturesFlow] for a simple example flow. Flows are started via a node's [ServiceHub].
|
|
||||||
|
|
||||||
Corda flows are a tool for modelling the interactions between two or more nodes as they negotiate a workflow.
|
|
||||||
This can range from a simple case of completing a trade which has been agreed upon externally, to more complex
|
|
||||||
processes such as handling fixing of interest rate swaps.
|
|
||||||
|
|
||||||
# Package net.corda.core.identity
|
|
||||||
|
|
||||||
Data classes which model different forms of identity (potentially with supporting evidence) for legal entities and services.
|
|
||||||
|
|
||||||
# Package net.corda.core.messaging
|
|
||||||
|
|
||||||
Data types used by the Corda messaging layer to manage state of messaging and sessions between nodes.
|
|
||||||
|
|
||||||
# Package net.corda.core.node.services
|
|
||||||
|
|
||||||
Services which run within a Corda node and provide various pieces of functionality such as identity management, transaction storage, etc.
|
|
||||||
|
|
||||||
# Package net.corda.core.node.services.vault
|
|
||||||
|
|
||||||
Supporting data types for the vault services.
|
|
||||||
|
|
||||||
# Package net.corda.core.schemas
|
|
||||||
|
|
||||||
Data types representing database schemas for storing Corda data via an object mapper such as Hibernate. Modifying Corda
|
|
||||||
state in the database directly is not a supported approach, however these can be used to read state for integrations with
|
|
||||||
external systems.
|
|
||||||
|
|
||||||
# Package net.corda.core.serialization
|
|
||||||
|
|
||||||
Supporting data types and classes for serialization of Corda data types.
|
|
||||||
|
|
||||||
# Package net.corda.core.transactions
|
|
||||||
|
|
||||||
Base data types for transactions which modify contract state on the distributed ledger.
|
|
||||||
|
|
||||||
The core transaction on the ledger is [WireTransaction], which is constructed by [TransactionBuilder]. Once signed a transaction is stored
|
|
||||||
in [SignedTransaction] which encapsulates [WireTransaction]. Finally there is a special-case [LedgerTransaction] which is used by contracts
|
|
||||||
validating transactions, and is built from the wire transaction by resolving all references into their underlying data (i.e. inputs are
|
|
||||||
actual states rather than state references).
|
|
||||||
|
|
||||||
# Package net.corda.core.utilities
|
|
||||||
|
|
||||||
Corda utility classes, providing a broad range of functionality to help implement both Corda nodes and CorDapps.
|
|
||||||
|
|
||||||
|
|
||||||
# Package net.corda.finance
|
|
||||||
|
|
||||||
Some simple testing utilities like pre-defined top-level values for common currencies. Mostly useful for
|
|
||||||
writing unit tests in Kotlin.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.utils
|
|
||||||
|
|
||||||
A collection of utilities for summing financial states, for example, summing obligations to get total debts.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.contracts
|
|
||||||
|
|
||||||
Various types for common financial concepts like day roll conventions, fixes, etc.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.contracts.asset
|
|
||||||
|
|
||||||
Cash states, obligations and commodities.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.contracts.asset.cash.selection
|
|
||||||
|
|
||||||
Provisional support for pluggable cash selectors, needed for different database backends.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.contracts.math
|
|
||||||
|
|
||||||
Splines and interpolation.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.flows
|
|
||||||
|
|
||||||
Cash payments and issuances. Two party "delivery vs payment" atomic asset swaps.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.plugin
|
|
||||||
|
|
||||||
JSON/Jackson plugin for business calendars.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.finance.schemas
|
|
||||||
|
|
||||||
JPA (Java Persistence Architecture) schemas for the financial state types.
|
|
||||||
|
|
||||||
__WARNING:__ This library is not suitable for production use and should not be used in real CorDapps.
|
|
||||||
Instead, use the [Token SDK](https://github.com/corda/token-sdk), or implement your own library. This
|
|
||||||
library may be removed in a future release without warning.
|
|
||||||
|
|
||||||
# Package net.corda.testing.core
|
|
||||||
|
|
||||||
Generic test utilities for contracts and flows
|
|
||||||
|
|
||||||
# Package net.corda.testing.node
|
|
||||||
|
|
||||||
Test utilites to help running nodes programmatically for tests
|
|
||||||
|
|
||||||
# Package net.corda.testing.driver
|
|
||||||
|
|
||||||
Test utilites to help running nodes programmatically for tests
|
|
||||||
|
|
||||||
# Package net.corda.testing.dsl
|
|
||||||
|
|
||||||
A simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes
|
|
||||||
|
|
||||||
# Package net.corda.testing.contracts
|
|
||||||
|
|
||||||
Dummy state and contracts for testing purposes
|
|
||||||
|
|
||||||
# Package net.corda.testing.services
|
|
||||||
|
|
||||||
Mock service implementations for testing purposes
|
|
||||||
|
|
||||||
# Package net.corda.testing.http
|
|
||||||
|
|
||||||
A small set of utilities for working with http calls.
|
|
||||||
|
|
||||||
WARNING: NOT API STABLE.
|
|
@ -1,29 +0,0 @@
|
|||||||
alabaster==0.7.8
|
|
||||||
Babel==2.3.4
|
|
||||||
certifi==2018.4.16
|
|
||||||
chardet==3.0.4
|
|
||||||
CommonMark==0.5.5
|
|
||||||
docutils==0.12
|
|
||||||
future==0.16.0
|
|
||||||
idna==2.6
|
|
||||||
imagesize==0.7.1
|
|
||||||
Jinja2==2.10.1
|
|
||||||
m2r==0.1.14
|
|
||||||
MarkupSafe==0.23
|
|
||||||
mistune==0.8.3
|
|
||||||
packaging==17.1
|
|
||||||
pdfrw==0.4
|
|
||||||
Pillow==6.2.0
|
|
||||||
Pygments==2.3.1
|
|
||||||
pyparsing==2.2.0
|
|
||||||
pytz==2016.4
|
|
||||||
reportlab==3.4.0
|
|
||||||
requests==2.20.0
|
|
||||||
rst2pdf==0.93
|
|
||||||
six==1.10.0
|
|
||||||
snowballstemmer==1.2.1
|
|
||||||
Sphinx==1.7.4
|
|
||||||
sphinx-rtd-theme==0.1.9
|
|
||||||
sphinxcontrib-websupport==1.0.1
|
|
||||||
typing==3.6.4
|
|
||||||
urllib3==1.24.2
|
|
@ -1,27 +0,0 @@
|
|||||||
$(document).ready(function() {
|
|
||||||
$(".codeset").each(function(index, el) {
|
|
||||||
var c = $("<div class='codesnippet-widgets'><span class='current'>Java</span><span>Kotlin</span></div>");
|
|
||||||
var javaButton = c.children()[0];
|
|
||||||
var kotlinButton = c.children()[1];
|
|
||||||
kotlinButton.onclick = function() {
|
|
||||||
$(el).children(".highlight-java")[0].style.display = "none";
|
|
||||||
$(el).children(".highlight-kotlin")[0].style.display = "block";
|
|
||||||
javaButton.setAttribute("class", "");
|
|
||||||
kotlinButton.setAttribute("class", "current");
|
|
||||||
};
|
|
||||||
javaButton.onclick = function() {
|
|
||||||
$(el).children(".highlight-java")[0].style.display = "block";
|
|
||||||
$(el).children(".highlight-kotlin")[0].style.display = "none";
|
|
||||||
kotlinButton.setAttribute("class", "");
|
|
||||||
javaButton.setAttribute("class", "current");
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($(el).children(".highlight-java").length == 0) {
|
|
||||||
// No Java for this example.
|
|
||||||
javaButton.style.display = "none";
|
|
||||||
// In this case, display Kotlin by default
|
|
||||||
$(el).children(".highlight-kotlin")[0].style.display = "block";
|
|
||||||
}
|
|
||||||
c.insertBefore(el);
|
|
||||||
});
|
|
||||||
});
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,221 +0,0 @@
|
|||||||
@import "theme.css";
|
|
||||||
|
|
||||||
/* Highlights */
|
|
||||||
|
|
||||||
.highlight .k,
|
|
||||||
.highlight .kd {
|
|
||||||
color: #263673;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Text */
|
|
||||||
|
|
||||||
body,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
.rst-content .toctree-wrapper p.caption,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
legend,
|
|
||||||
input {
|
|
||||||
color: #010101;
|
|
||||||
letter-spacing: 0.3px
|
|
||||||
}
|
|
||||||
|
|
||||||
p.caption {
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
span.caption-text {
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 100%; /* Get rid of RTD rule that assumes nobody changes their browser font size */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Links */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #263673
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #005CAB
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
color: #ADAFB3
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Side navigation bar */
|
|
||||||
|
|
||||||
.wy-side-nav-search {
|
|
||||||
background-color: #252627;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-side-nav-search a.icon-home {
|
|
||||||
color: transparent;
|
|
||||||
background-image: url('../images/fg002_corda_w3.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: Auto 20px;
|
|
||||||
background-position: center top;
|
|
||||||
background-origin: content box;
|
|
||||||
height: 20px;
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-side-nav-search input[type=text] {
|
|
||||||
border-radius: 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-menu-vertical a:hover {
|
|
||||||
background-color: #ADAFB3;
|
|
||||||
color: #FFF
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-nav-content {
|
|
||||||
background-color: #fff;
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-nav-side {
|
|
||||||
background-color: #252627;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Navigation headers */
|
|
||||||
|
|
||||||
.rst-content tt.literal,
|
|
||||||
.rst-content tt.literal,
|
|
||||||
.rst-content code.literal {
|
|
||||||
color: #EC1D24;
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-menu-vertical header,
|
|
||||||
.wy-menu-vertical p.caption {
|
|
||||||
color: #EC1D24;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Code snippets */
|
|
||||||
|
|
||||||
.codesnippet-widgets {
|
|
||||||
min-width: 100%;
|
|
||||||
display: block;
|
|
||||||
background: #005CAB;
|
|
||||||
color: white;
|
|
||||||
padding: 10px 0;
|
|
||||||
margin: 0 0 -1px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codesnippet-widgets > span {
|
|
||||||
padding: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codesnippet-widgets > .current {
|
|
||||||
background: #263673;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codeset > .highlight-kotlin {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Notification boxes */
|
|
||||||
|
|
||||||
.wy-alert.wy-alert-warning .wy-alert-title,
|
|
||||||
.rst-content .wy-alert-warning.note .wy-alert-title,
|
|
||||||
.rst-content .attention .wy-alert-title,
|
|
||||||
.rst-content .caution .wy-alert-title,
|
|
||||||
.rst-content .wy-alert-warning.danger .wy-alert-title,
|
|
||||||
.rst-content .wy-alert-warning.error .wy-alert-title,
|
|
||||||
.rst-content .wy-alert-warning.hint .wy-alert-title,
|
|
||||||
.rst-content .wy-alert-warning.important .wy-alert-title,
|
|
||||||
.rst-content .wy-alert-warning.tip .wy-alert-title,
|
|
||||||
.rst-content .warning .wy-alert-title,
|
|
||||||
.rst-content .wy-alert-warning.seealso .wy-alert-title,
|
|
||||||
.rst-content .admonition-todo .wy-alert-title,
|
|
||||||
.wy-alert.wy-alert-warning .rst-content .admonition-title,
|
|
||||||
.rst-content .wy-alert.wy-alert-warning .admonition-title,
|
|
||||||
.rst-content .wy-alert-warning.note .admonition-title,
|
|
||||||
.rst-content .attention .admonition-title,
|
|
||||||
.rst-content .caution .admonition-title,
|
|
||||||
.rst-content .wy-alert-warning.danger .admonition-title,
|
|
||||||
.rst-content .wy-alert-warning.error .admonition-title,
|
|
||||||
.rst-content .wy-alert-warning.hint .admonition-title,
|
|
||||||
.rst-content .wy-alert-warning.important .admonition-title,
|
|
||||||
.rst-content .wy-alert-warning.tip .admonition-title,
|
|
||||||
.rst-content .warning .admonition-title,
|
|
||||||
.rst-content .wy-alert-warning.seealso .admonition-title,
|
|
||||||
.rst-content .admonition-todo .admonition-title {
|
|
||||||
background-color: #263673
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-alert,
|
|
||||||
.rst-content .note,
|
|
||||||
.rst-content .attention,
|
|
||||||
.rst-content .caution,
|
|
||||||
.rst-content .danger,
|
|
||||||
.rst-content .error,
|
|
||||||
.rst-content .hint,
|
|
||||||
.rst-content .important,
|
|
||||||
.rst-content .tip,
|
|
||||||
.rst-content .warning,
|
|
||||||
.rst-content .seealso,
|
|
||||||
.rst-content .admonition-todo {
|
|
||||||
background-color: #d9e5ef
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Mobile view */
|
|
||||||
|
|
||||||
.wy-nav-top {
|
|
||||||
background-color: #252627;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-nav-top a {
|
|
||||||
color: transparent;
|
|
||||||
background-image: url('../images/fg002_corda_w3.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: Auto 19px;
|
|
||||||
background-position: center top;
|
|
||||||
background-origin: content box;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Version dropdown */
|
|
||||||
|
|
||||||
.version-dropdown {
|
|
||||||
border-radius: 4px;
|
|
||||||
border-color: #263673;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hover buttons */
|
|
||||||
.button {
|
|
||||||
background-color: #4CAF50; /* Green */
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
padding: 15px 32px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 4px 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-transition-duration: 0.4s; /* Safari */
|
|
||||||
transition-duration: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button1 {
|
|
||||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button2:hover {
|
|
||||||
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 216 KiB |
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"https://docs.corda.net/releases/release-M6.0": "M6.0",
|
|
||||||
"https://docs.corda.net/releases/release-M7.0": "M7.0",
|
|
||||||
"https://docs.corda.net/releases/release-M8.2": "M8.2",
|
|
||||||
"https://docs.corda.net/releases/release-M9.2": "M9.2",
|
|
||||||
"https://docs.corda.net/releases/release-M10.1": "M10.1",
|
|
||||||
"https://docs.corda.net/releases/release-M11.2": "M11.2",
|
|
||||||
"https://docs.corda.net/releases/release-M12.1": "M12.1",
|
|
||||||
"https://docs.corda.net/releases/release-M13.0": "M13.0",
|
|
||||||
"https://docs.corda.net/releases/release-M14.0": "M14.0",
|
|
||||||
"https://docs.corda.net/releases/release-V1.0": "V1.0",
|
|
||||||
"https://docs.corda.net/releases/release-V2.0": "V2.0",
|
|
||||||
"https://docs.corda.net/releases/release-V3.0": "V3.0",
|
|
||||||
"https://docs.corda.net/releases/release-V3.1": "V3.1",
|
|
||||||
"https://docs.corda.net/releases/release-V3.2": "V3.2",
|
|
||||||
"https://docs.corda.net/releases/release-V3.3": "V3.3",
|
|
||||||
"https://docs.corda.net/releases/release-V4.0": "V4.0",
|
|
||||||
"https://docs.corda.net/releases/release-V4.1": "V4.1",
|
|
||||||
"https://docs.corda.net/head/": "Master"
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
<!-- 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 -->
|
|
||||||
|
|
||||||
|
|
||||||
{% extends "!layout.html" %}
|
|
||||||
{% block sidebartitle %}
|
|
||||||
{{ super() }}
|
|
||||||
<br>
|
|
||||||
API reference: <a href="api/kotlin/corda/index.html">Kotlin</a>/ <a href="api/javadoc/index.html">JavaDoc</a>
|
|
||||||
<br>
|
|
||||||
<a href="http://slack.corda.net">Slack</a>
|
|
||||||
<br>
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/corda">Stack Overflow</a>
|
|
||||||
<br>
|
|
||||||
{% endblock %}
|
|
@ -1,46 +0,0 @@
|
|||||||
<!-- -->
|
|
||||||
|
|
||||||
{% extends "!layout.html" %}
|
|
||||||
{% block sidebartitle %}
|
|
||||||
{{ super() }}
|
|
||||||
<br>
|
|
||||||
API reference: <a href="api/kotlin/corda/index.html">Kotlin</a>/ <a href="api/javadoc/index.html">JavaDoc</a>
|
|
||||||
<br>
|
|
||||||
<a href="http://slack.corda.net">Slack</a>
|
|
||||||
<br>
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/corda">Stack Overflow</a>
|
|
||||||
<br>
|
|
||||||
<select id="versionDropdown" class="version-dropdown" onChange="window.location.href=this.value"></select>
|
|
||||||
<br>
|
|
||||||
<span style="display:none" id="version">{{ version }}</span>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block footer %}
|
|
||||||
<script>
|
|
||||||
// A synchronous request to retrieve all the Corda versions.
|
|
||||||
$.getJSON("https://docs.corda.net/_static/versions", function(data) {
|
|
||||||
// Grab the current version.
|
|
||||||
var version = $("#version").html();
|
|
||||||
|
|
||||||
// We populate the version dropdown.
|
|
||||||
$.each(data, function(link, text) {
|
|
||||||
if (text === version) {
|
|
||||||
$('#versionDropdown').append($('<option>').text(text).attr('value', link).prop('selected', true));
|
|
||||||
} else {
|
|
||||||
$('#versionDropdown').append($('<option>').text(text).attr('value', link));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-87760032-1', 'auto');
|
|
||||||
ga('send', 'pageview');
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@ -1,422 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. role:: kotlin(code)
|
|
||||||
:language: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Contract Constraints
|
|
||||||
=========================
|
|
||||||
|
|
||||||
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
|
|
||||||
|
|
||||||
.. note:: As of Corda |corda_version| the `minimumPlatformVersion` required to use these features is 4
|
|
||||||
(see :ref:`Network Parameters <network-parameters>` and :doc:`features-versions` for more details).
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Reasons for Contract Constraints
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
*Contract constraints* solve two problems faced by any decentralised ledger that supports evolution of data and code:
|
|
||||||
|
|
||||||
1. Controlling and agreeing upon upgrades
|
|
||||||
2. Preventing attacks
|
|
||||||
|
|
||||||
Upgrades and security are intimately related because if an attacker can "upgrade" your data to a version of an app that gives them
|
|
||||||
a back door, they would be able to do things like print money or edit states in any way they want. That's why it's important for
|
|
||||||
participants of a state to agree on what kind of upgrades will be allowed.
|
|
||||||
|
|
||||||
Every state on the ledger contains the fully qualified class name of a ``Contract`` implementation, and also a *constraint*.
|
|
||||||
This constraint specifies which versions of an application can be used to provide the named class, when the transaction is built.
|
|
||||||
New versions released after a transaction is signed and finalised won't affect prior transactions because the old code is attached
|
|
||||||
to it.
|
|
||||||
|
|
||||||
.. _implicit_vs_explicit_upgrades:
|
|
||||||
|
|
||||||
Implicit vs Explicit Contract upgrades
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Constraints are not the only way to manage upgrades to transactions. There are two ways of handling
|
|
||||||
upgrades to a smart contract in Corda:
|
|
||||||
|
|
||||||
* **Implicit**: By pre-authorising multiple implementations of the contract ahead of time, using constraints.
|
|
||||||
* **Explicit**: By creating a special *contract upgrade transaction* and getting all participants of a state to sign it using the
|
|
||||||
contract upgrade flows.
|
|
||||||
|
|
||||||
The advantage of pre-authorising upgrades using constraints is that you don't need the heavyweight process of creating
|
|
||||||
upgrade transactions for every state on the ledger. The disadvantage is that you place more faith in third parties,
|
|
||||||
who could potentially change the app in ways you did not expect or agree with. The advantage of using the explicit
|
|
||||||
upgrade approach is that you can upgrade states regardless of their constraint, including in cases where you didn't
|
|
||||||
anticipate a need to do so. But it requires everyone to sign, manually authorise the upgrade,
|
|
||||||
consumes notary and ledger resources, and is just in general more complex.
|
|
||||||
|
|
||||||
This article focuses on the first approach. To learn about the second please see :doc:`upgrading-cordapps`.
|
|
||||||
|
|
||||||
.. _implicit_constraint_types:
|
|
||||||
|
|
||||||
Types of Contract Constraints
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Corda supports several types of constraints to cover a wide set of client requirements:
|
|
||||||
|
|
||||||
* **Hash constraint**: Exactly one version of the app can be used with this state. This prevents the app from being upgraded in the future while still
|
|
||||||
making use of the state created with the original version.
|
|
||||||
* **Compatibility zone whitelisted (or CZ whitelisted) constraint**: The compatibility zone operator lists the hashes of the versions that can be used with a contract class name.
|
|
||||||
* **Signature constraint**: Any version of the app signed by the given ``CompositeKey`` can be used. This allows app issuers to express the
|
|
||||||
complex social and business relationships that arise around code ownership. For example, a Signature Constraint allows a new version of an
|
|
||||||
app to be produced and applied to an existing state as long as it has been signed by the same key(s) as the original version.
|
|
||||||
* **Always accept constraint**: Any version of the app can be used. This is insecure but convenient for testing.
|
|
||||||
|
|
||||||
.. _signature_constraints:
|
|
||||||
|
|
||||||
Signature Constraints
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The best kind of constraint to use is the **Signature Constraint**. If you sign your application it will be used automatically.
|
|
||||||
We recommend signature constraints because they let you express complex social and business relationships while allowing
|
|
||||||
smooth migration of existing data to new versions of your application.
|
|
||||||
|
|
||||||
Signature constraints can specify flexible threshold policies, but if you use the automatic support then a state will
|
|
||||||
require the attached app to be signed by every key that the first attachment was signed by. Thus if the app that was used
|
|
||||||
to issue the states was signed by Alice and Bob, every transaction must use an attachment signed by Alice and Bob. Doing so allows the
|
|
||||||
app to be upgraded and changed while still remaining valid for use with the previously issued states.
|
|
||||||
|
|
||||||
More complex policies can be expressed through Signature Constraints if required. Allowing policies where only a number of the possible
|
|
||||||
signers must sign the new version of an app that is interacting with previously issued states. Accepting different versions of apps in this
|
|
||||||
way makes it possible for multiple versions to be valid across the network as long as the majority (or possibly a minority) agree with the
|
|
||||||
logic provided by the apps.
|
|
||||||
|
|
||||||
Hash and zone whitelist constraints are left over from earlier Corda versions before Signature Constraints were
|
|
||||||
implemented. They make it harder to upgrade applications than when using signature constraints, so they're best avoided.
|
|
||||||
|
|
||||||
.. _signing_cordapps_for_use_with_signature_constraints:
|
|
||||||
|
|
||||||
Signing CorDapps for use with Signature Constraints
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Expanding on the previous section, for an app to use Signature Constraints, it must be signed by a ``CompositeKey`` or a simpler ``PublicKey``.
|
|
||||||
The signers of the app can consist of a single organisation or multiple organisations. Once the app has been signed, it can be distributed
|
|
||||||
across the nodes that intend to use it.
|
|
||||||
|
|
||||||
.. note:: The platform currently supports ``CompositeKey``\s with up to 20 keys maximum.
|
|
||||||
This maximum limit is assuming keys that are either 2048-bit ``RSA`` keys or 256-bit elliptic curve (``EC``) keys.
|
|
||||||
|
|
||||||
Each transaction received by a node will then verify that the apps attached to it have the correct signers as specified by its
|
|
||||||
Signature Constraints. This ensures that the version of each app is acceptable to the transaction's input states.
|
|
||||||
|
|
||||||
If a node receives a transaction that uses an attachment that it doesn't trust, but there is another attachment present on the node with
|
|
||||||
at least one common signature, then the node will trust the received attachment. This means that nodes
|
|
||||||
are no longer required to have every version of a CorDapp uploaded to them in order to verify transactions running older versions of a CorDapp.
|
|
||||||
Instead, it is sufficient to have any version of the CorDapp contract installed.
|
|
||||||
|
|
||||||
.. note:: An attachment is considered trusted if it was manually installed or uploaded via RPC.
|
|
||||||
|
|
||||||
Signers can also be blacklisted to prevent attachments received from a peer from being loaded and used in processing transactions. Only a
|
|
||||||
single signer of an attachment needs to be blacklisted for an attachment to be considered untrusted. CorDapps
|
|
||||||
and other attachments installed on a node can still be used without issue, even if they are signed by a blacklisted key. Only attachments
|
|
||||||
received from a peer are affected.
|
|
||||||
|
|
||||||
Below are two examples of possible scenarios around blacklisting signing keys:
|
|
||||||
|
|
||||||
- The statements below are true for both examples:
|
|
||||||
|
|
||||||
- ``Alice`` has ``Contracts CorDapp`` installed
|
|
||||||
- ``Bob`` has an upgraded version of ``Contracts CorDapp`` (known as ``Contracts CorDapp V2``) installed
|
|
||||||
- Both ``Alice`` and ``Bob`` have the ``Workflows CorDapp`` allowing them to transact with each other
|
|
||||||
- ``Contracts CorDapp`` is signed by both ``Alice`` and ``Bob``
|
|
||||||
- ``Contracts CorDapp V2`` is signed by both ``Alice`` and ``Bob``
|
|
||||||
|
|
||||||
- Example 1:
|
|
||||||
|
|
||||||
- ``Alice`` has not blacklisted any attachment signing keys
|
|
||||||
- ``Bob`` transacts with ``Alice``
|
|
||||||
- ``Alice`` receives ``Contracts CorDapp V2`` and stores it
|
|
||||||
- When verifying the attachments loaded into the contract verification code, ``Contracts CorDapp V2`` is accepted and used
|
|
||||||
- The contract verification code in ``Contracts CorDapp V2`` is run
|
|
||||||
|
|
||||||
- Example 2:
|
|
||||||
|
|
||||||
- ``Alice`` blacklists ``Bob``'s attachment signing key
|
|
||||||
- ``Bob`` transacts with ``Alice``
|
|
||||||
- ``Alice`` receives ``Contracts CorDapp V2`` and stores it
|
|
||||||
- When verifying the attachments loaded in the contract verification code, ``Contracts CorDapp V2`` is declined because it is signed
|
|
||||||
by ``Bob``'s blacklisted key
|
|
||||||
- The contract verification code in ``Contracts CorDapp V2`` is not run and the transaction fails
|
|
||||||
|
|
||||||
Information on blacklisting attachment signing keys can be found in the
|
|
||||||
:ref:`node configuration documentation <corda_configuration_file_blacklisted_attachment_signer_keys>`.
|
|
||||||
|
|
||||||
More information on how to sign an app directly from Gradle can be found in the
|
|
||||||
:ref:`CorDapp Jar signing <cordapp_build_system_signing_cordapp_jar_ref>` section of the documentation.
|
|
||||||
|
|
||||||
Using Signature Constraints in transactions
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
If the app is signed, Signature Constraints will be used by default (in most situations) by the ``TransactionBuilder`` when adding output states.
|
|
||||||
This is expanded upon in :ref:`contract_constraints_in_transactions`.
|
|
||||||
|
|
||||||
.. note:: Signature Constraints are used by default except when a new transaction contains an input state with a Hash Constraint. In this
|
|
||||||
situation the Hash Constraint is used.
|
|
||||||
|
|
||||||
App versioning with Signature Constraints
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Signed apps require a version number to be provided, see :doc:`versioning`.
|
|
||||||
|
|
||||||
Hash Constraints
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Issues when using the HashAttachmentConstraint
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
When setting up a new network, it is possible to encounter errors when states are issued with the ``HashAttachmentConstraint``,
|
|
||||||
but not all nodes have that same version of the CorDapp installed locally.
|
|
||||||
|
|
||||||
In this case, flows will fail with a ``ContractConstraintRejection``, and are sent to the flow hospital.
|
|
||||||
From there, they are suspended, waiting to be retried on node restart.
|
|
||||||
This gives the node operator the opportunity to recover from those errors, which in the case of constraint violations means
|
|
||||||
adding the right cordapp jar to the ``cordapps`` folder.
|
|
||||||
|
|
||||||
.. _relax_hash_constraints_checking_ref:
|
|
||||||
|
|
||||||
Hash constrained states in private networks
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Where private networks started life using CorDapps with hash constrained states, we have introduced a mechanism to relax the checking of
|
|
||||||
these hash constrained states when upgrading to signed CorDapps using signature constraints.
|
|
||||||
|
|
||||||
The Java system property ``-Dnet.corda.node.disableHashConstraints="true"`` may be set to relax the hash constraint checking behaviour. For
|
|
||||||
this to work, every participant of the network must set the property to the same value. Therefore, this mode should only be used upon
|
|
||||||
"out of band" agreement by all participants in a network.
|
|
||||||
|
|
||||||
.. warning:: This flag should remain enabled until every hash constrained state is exited from the ledger.
|
|
||||||
|
|
||||||
.. _contract_state_agreement:
|
|
||||||
|
|
||||||
Contract/State Agreement
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
Starting with Corda 4, a ``ContractState`` must explicitly indicate which ``Contract`` it belongs to. When a transaction is
|
|
||||||
verified, the contract bundled with each state in the transaction must be its "owning" contract, otherwise we cannot guarantee that
|
|
||||||
the transition of the ``ContractState`` will be verified against the business rules that should apply to it.
|
|
||||||
|
|
||||||
There are two mechanisms for indicating ownership. One is to annotate the ``ContractState`` with the ``BelongsToContract`` annotation,
|
|
||||||
indicating the ``Contract`` class to which it is tied:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
@BelongsToContract(MyContract.class)
|
|
||||||
public class MyState implements ContractState {
|
|
||||||
// implementation goes here
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
@BelongsToContract(MyContract::class)
|
|
||||||
data class MyState(val value: Int) : ContractState {
|
|
||||||
// implementation goes here
|
|
||||||
}
|
|
||||||
|
|
||||||
The other is to define the ``ContractState`` class as an inner class of the ``Contract`` class:
|
|
||||||
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
public class MyContract implements Contract {
|
|
||||||
|
|
||||||
public static class MyState implements ContractState {
|
|
||||||
// state implementation goes here
|
|
||||||
}
|
|
||||||
|
|
||||||
// contract implementation goes here
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
class MyContract : Contract {
|
|
||||||
|
|
||||||
data class MyState(val value: Int) : ContractState {
|
|
||||||
// state implementation goes here
|
|
||||||
}
|
|
||||||
|
|
||||||
// contract implementation goes here
|
|
||||||
}
|
|
||||||
|
|
||||||
If a ``ContractState``'s owning ``Contract`` cannot be identified by either of these mechanisms, and the ``targetVersion`` of the
|
|
||||||
CorDapp is 4 or greater, then transaction verification will fail with a ``TransactionRequiredContractUnspecifiedException``. If
|
|
||||||
the owning ``Contract`` *can* be identified, but the ``ContractState`` has been bundled with a different contract, then
|
|
||||||
transaction verification will fail with a ``TransactionContractConflictException``.
|
|
||||||
|
|
||||||
.. _contract_constraints_in_transactions:
|
|
||||||
|
|
||||||
Using Contract Constraints in Transactions
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
The app version used by a transaction is defined by its attachments. The JAR containing the state and contract classes, and optionally its
|
|
||||||
dependencies, are all attached to the transaction. Nodes will download this JAR from other nodes if they haven't seen it before,
|
|
||||||
so it can be used for verification.
|
|
||||||
|
|
||||||
The ``TransactionBuilder`` will manage the details of constraints for you, by selecting both constraints
|
|
||||||
and attachments to ensure they line up correctly. Therefore you only need to have a basic understanding of this topic unless you are
|
|
||||||
doing something sophisticated.
|
|
||||||
|
|
||||||
By default the ``TransactionBuilder`` will use :ref:`signature_constraints` for any issuance transactions if the app attached to it is
|
|
||||||
signed.
|
|
||||||
|
|
||||||
To manually define the Contract Constraint of an output state, see the example below:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
TransactionBuilder transaction() {
|
|
||||||
TransactionBuilder transaction = new TransactionBuilder(notary());
|
|
||||||
// Signature Constraint used if app is signed
|
|
||||||
transaction.addOutputState(state);
|
|
||||||
// Explicitly using a Signature Constraint
|
|
||||||
transaction.addOutputState(state, CONTRACT_ID, new SignatureAttachmentConstraint(getOurIdentity().getOwningKey()));
|
|
||||||
// Explicitly using a Hash Constraint
|
|
||||||
transaction.addOutputState(state, CONTRACT_ID, new HashAttachmentConstraint(getServiceHub().getCordappProvider().getContractAttachmentID(CONTRACT_ID)));
|
|
||||||
// Explicitly using a Whitelisted by Zone Constraint
|
|
||||||
transaction.addOutputState(state, CONTRACT_ID, WhitelistedByZoneAttachmentConstraint.INSTANCE);
|
|
||||||
// Explicitly using an Always Accept Constraint
|
|
||||||
transaction.addOutputState(state, CONTRACT_ID, AlwaysAcceptAttachmentConstraint.INSTANCE);
|
|
||||||
|
|
||||||
// other transaction stuff
|
|
||||||
return transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
private fun transaction(): TransactionBuilder {
|
|
||||||
val transaction = TransactionBuilder(notary())
|
|
||||||
// Signature Constraint used if app is signed
|
|
||||||
transaction.addOutputState(state)
|
|
||||||
// Explicitly using a Signature Constraint
|
|
||||||
transaction.addOutputState(state, constraint = SignatureAttachmentConstraint(ourIdentity.owningKey))
|
|
||||||
// Explicitly using a Hash Constraint
|
|
||||||
transaction.addOutputState(state, constraint = HashAttachmentConstraint(serviceHub.cordappProvider.getContractAttachmentID(CONTRACT_ID)!!))
|
|
||||||
// Explicitly using a Whitelisted by Zone Constraint
|
|
||||||
transaction.addOutputState(state, constraint = WhitelistedByZoneAttachmentConstraint)
|
|
||||||
// Explicitly using an Always Accept Constraint
|
|
||||||
transaction.addOutputState(state, constraint = AlwaysAcceptAttachmentConstraint)
|
|
||||||
|
|
||||||
// other transaction stuff
|
|
||||||
return transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
CorDapps as attachments
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
CorDapp JARs (see :doc:`cordapp-overview`) that contain classes implementing the ``Contract`` interface are automatically
|
|
||||||
loaded into the ``AttachmentStorage`` of a node, and made available as ``ContractAttachments``.
|
|
||||||
|
|
||||||
They are retrievable by hash using ``AttachmentStorage.openAttachment``. These JARs can either be installed on the
|
|
||||||
node or will be automatically fetched over the network when receiving a transaction.
|
|
||||||
|
|
||||||
.. warning:: The obvious way to write a CorDapp is to put all you states, contracts, flows and support code into a single
|
|
||||||
Java module. This will work but it will effectively publish your entire app onto the ledger. That has two problems:
|
|
||||||
(1) it is inefficient, and (2) it means changes to your flows or other parts of the app will be seen by the ledger
|
|
||||||
as a "new app", which may end up requiring essentially unnecessary upgrade procedures. It's better to split your
|
|
||||||
app into multiple modules: one which contains just states, contracts and core data types. And another which contains
|
|
||||||
the rest of the app. See :ref:`cordapp-structure`.
|
|
||||||
|
|
||||||
.. _constraints_propagation:
|
|
||||||
|
|
||||||
Constraints propagation
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
As was mentioned above, the ``TransactionBuilder`` API gives the CorDapp developer or even malicious node owner the possibility
|
|
||||||
to construct output states with a constraint of their choosing.
|
|
||||||
|
|
||||||
For the ledger to remain in a consistent state, the expected behavior is for output state to inherit the constraints of input states.
|
|
||||||
This guarantees that for example, a transaction can't output a state with the ``AlwaysAcceptAttachmentConstraint`` when the
|
|
||||||
corresponding input state was the ``SignatureAttachmentConstraint``. Translated, this means that if this rule is enforced, it ensures
|
|
||||||
that the output state will be spent under similar conditions as it was created.
|
|
||||||
|
|
||||||
Before version 4, the constraint propagation logic was expected to be enforced in the contract verify code, as it has access to the entire Transaction.
|
|
||||||
|
|
||||||
Starting with version 4 of Corda the constraint propagation logic has been implemented and enforced directly by the platform,
|
|
||||||
unless disabled by putting ``@NoConstraintPropagation`` on the ``Contract`` class which reverts to the previous behavior of expecting
|
|
||||||
apps to do this.
|
|
||||||
|
|
||||||
For contracts that are not annotated with ``@NoConstraintPropagation``, the platform implements a fairly simple constraint transition policy
|
|
||||||
to ensure security and also allow the possibility to transition to the new ``SignatureAttachmentConstraint``.
|
|
||||||
|
|
||||||
During transaction building the ``AutomaticPlaceholderConstraint`` for output states will be resolved and the best contract attachment versions
|
|
||||||
will be selected based on a variety of factors so that the above holds true. If it can't find attachments in storage or there are no
|
|
||||||
possible constraints, the ``TransactionBuilder`` will throw an exception.
|
|
||||||
|
|
||||||
Constraints migration to Corda 4
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Please read :doc:`cordapp-constraint-migration` to understand how to consume and evolve pre-Corda 4 issued hash or CZ whitelisted constrained states
|
|
||||||
using a Corda 4 signed CorDapp (using signature constraints).
|
|
||||||
|
|
||||||
Debugging
|
|
||||||
---------
|
|
||||||
If an attachment constraint cannot be resolved, a ``MissingContractAttachments`` exception is thrown. There are three common sources of
|
|
||||||
``MissingContractAttachments`` exceptions:
|
|
||||||
|
|
||||||
Not setting CorDapp packages in tests
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
You are running a test and have not specified the CorDapp packages to scan.
|
|
||||||
When using ``MockNetwork`` ensure you have provided a package containing the contract class in ``MockNetworkParameters``. See :doc:`api-testing`.
|
|
||||||
|
|
||||||
Similarly package names need to be provided when testing using ``DriverDSl``. ``DriverParameters`` has a property ``cordappsForAllNodes`` (Kotlin)
|
|
||||||
or method ``withCordappsForAllNodes`` in Java. Pass the collection of ``TestCordapp`` created by utility method ``TestCordapp.findCordapp(String)``.
|
|
||||||
|
|
||||||
Example of creation of two Cordapps with Finance App Flows and Finance App Contracts:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
Driver.driver(DriverParameters(
|
|
||||||
cordappsForAllNodes = listOf(
|
|
||||||
TestCordapp.findCordapp("net.corda.finance.schemas"),
|
|
||||||
TestCordapp.findCordapp("net.corda.finance.flows")
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Your test code goes here
|
|
||||||
})
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
Driver.driver(
|
|
||||||
new DriverParameters()
|
|
||||||
.withCordappsForAllNodes(
|
|
||||||
Arrays.asList(
|
|
||||||
TestCordapp.findCordapp("net.corda.finance.schemas"),
|
|
||||||
TestCordapp.findCordapp("net.corda.finance.flows")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
dsl -> {
|
|
||||||
// Your test code goes here
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Starting a node missing CorDapp(s)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
When running the Corda node ensure all CordDapp JARs are placed in ``cordapps`` directory of each node.
|
|
||||||
By default Gradle Cordform task ``deployNodes`` copies all JARs if CorDapps to deploy are specified.
|
|
||||||
See :doc:`generating-a-node` for detailed instructions.
|
|
||||||
|
|
||||||
Wrong fully-qualified contract name
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
You are specifying the fully-qualified name of the contract incorrectly. For example, you've defined ``MyContract`` in
|
|
||||||
the package ``com.mycompany.myapp.contracts``, but the fully-qualified contract name you pass to the
|
|
||||||
``TransactionBuilder`` is ``com.mycompany.myapp.MyContract`` (instead of ``com.mycompany.myapp.contracts.MyContract``).
|
|
@ -1,236 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Contracts
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Contract
|
|
||||||
--------
|
|
||||||
Contracts are classes that implement the ``Contract`` interface. The ``Contract`` interface is defined as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 5
|
|
||||||
:end-before: DOCEND 5
|
|
||||||
|
|
||||||
``Contract`` has a single method, ``verify``, which takes a ``LedgerTransaction`` as input and returns
|
|
||||||
nothing. This function is used to check whether a transaction proposal is valid, as follows:
|
|
||||||
|
|
||||||
* We gather together the contracts of each of the transaction's input and output states
|
|
||||||
* We call each contract's ``verify`` function, passing in the transaction as an input
|
|
||||||
* The proposal is only valid if none of the ``verify`` calls throw an exception
|
|
||||||
|
|
||||||
``verify`` is executed in a sandbox:
|
|
||||||
|
|
||||||
* It does not have access to the enclosing scope
|
|
||||||
* The libraries available to it are whitelisted to disallow:
|
|
||||||
* Network access
|
|
||||||
* I/O such as disk or database access
|
|
||||||
* Sources of randomness such as the current time or random number generators
|
|
||||||
|
|
||||||
This means that ``verify`` only has access to the properties defined on ``LedgerTransaction`` when deciding whether a
|
|
||||||
transaction is valid.
|
|
||||||
|
|
||||||
Here are the two simplest ``verify`` functions:
|
|
||||||
|
|
||||||
* A ``verify`` that **accepts** all possible transactions:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
override fun verify(tx: LedgerTransaction) {
|
|
||||||
// Always accepts!
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verify(LedgerTransaction tx) {
|
|
||||||
// Always accepts!
|
|
||||||
}
|
|
||||||
|
|
||||||
* A ``verify`` that **rejects** all possible transactions:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
override fun verify(tx: LedgerTransaction) {
|
|
||||||
throw IllegalArgumentException("Always rejects!")
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verify(LedgerTransaction tx) {
|
|
||||||
throw new IllegalArgumentException("Always rejects!");
|
|
||||||
}
|
|
||||||
|
|
||||||
LedgerTransaction
|
|
||||||
-----------------
|
|
||||||
The ``LedgerTransaction`` object passed into ``verify`` has the following properties:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
* ``inputs`` are the transaction's inputs as ``List<StateAndRef<ContractState>>``
|
|
||||||
* ``outputs`` are the transaction's outputs as ``List<TransactionState<ContractState>>``
|
|
||||||
* ``commands`` are the transaction's commands and associated signers, as ``List<CommandWithParties<CommandData>>``
|
|
||||||
* ``attachments`` are the transaction's attachments as ``List<Attachment>``
|
|
||||||
* ``notary`` is the transaction's notary. This must match the notary of all the inputs
|
|
||||||
* ``timeWindow`` defines the window during which the transaction can be notarised
|
|
||||||
|
|
||||||
``LedgerTransaction`` exposes a large number of utility methods to access the transaction's contents:
|
|
||||||
|
|
||||||
* ``inputStates`` extracts the input ``ContractState`` objects from the list of ``StateAndRef``
|
|
||||||
* ``getInput``/``getOutput``/``getCommand``/``getAttachment`` extracts a component by index
|
|
||||||
* ``getAttachment`` extracts an attachment by ID
|
|
||||||
* ``inputsOfType``/``inRefsOfType``/``outputsOfType``/``outRefsOfType``/``commandsOfType`` extracts components based on
|
|
||||||
their generic type
|
|
||||||
* ``filterInputs``/``filterInRefs``/``filterOutputs``/``filterOutRefs``/``filterCommands`` extracts components based on
|
|
||||||
a predicate
|
|
||||||
* ``findInput``/``findInRef``/``findOutput``/``findOutRef``/``findCommand`` extracts the single component that matches
|
|
||||||
a predicate, or throws an exception if there are multiple matches
|
|
||||||
|
|
||||||
requireThat
|
|
||||||
-----------
|
|
||||||
``verify`` can be written to manually throw an exception for each constraint:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
override fun verify(tx: LedgerTransaction) {
|
|
||||||
if (tx.inputs.size > 0)
|
|
||||||
throw IllegalArgumentException("No inputs should be consumed when issuing an X.")
|
|
||||||
|
|
||||||
if (tx.outputs.size != 1)
|
|
||||||
throw IllegalArgumentException("Only one output state should be created.")
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
public void verify(LedgerTransaction tx) {
|
|
||||||
if (tx.getInputs().size() > 0)
|
|
||||||
throw new IllegalArgumentException("No inputs should be consumed when issuing an X.");
|
|
||||||
|
|
||||||
if (tx.getOutputs().size() != 1)
|
|
||||||
throw new IllegalArgumentException("Only one output state should be created.");
|
|
||||||
}
|
|
||||||
|
|
||||||
However, this is verbose. To impose a series of constraints, we can use ``requireThat`` instead:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
requireThat {
|
|
||||||
"No inputs should be consumed when issuing an X." using (tx.inputs.isEmpty())
|
|
||||||
"Only one output state should be created." using (tx.outputs.size == 1)
|
|
||||||
val out = tx.outputs.single() as XState
|
|
||||||
"The sender and the recipient cannot be the same entity." using (out.sender != out.recipient)
|
|
||||||
"All of the participants must be signers." using (command.signers.containsAll(out.participants))
|
|
||||||
"The X's value must be non-negative." using (out.x.value > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
requireThat(require -> {
|
|
||||||
require.using("No inputs should be consumed when issuing an X.", tx.getInputs().isEmpty());
|
|
||||||
require.using("Only one output state should be created.", tx.getOutputs().size() == 1);
|
|
||||||
final XState out = (XState) tx.getOutputs().get(0);
|
|
||||||
require.using("The sender and the recipient cannot be the same entity.", out.getSender() != out.getRecipient());
|
|
||||||
require.using("All of the participants must be signers.", command.getSigners().containsAll(out.getParticipants()));
|
|
||||||
require.using("The X's value must be non-negative.", out.getX().getValue() > 0);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
For each <``String``, ``Boolean``> pair within ``requireThat``, if the boolean condition is false, an
|
|
||||||
``IllegalArgumentException`` is thrown with the corresponding string as the exception message. In turn, this
|
|
||||||
exception will cause the transaction to be rejected.
|
|
||||||
|
|
||||||
Commands
|
|
||||||
--------
|
|
||||||
``LedgerTransaction`` contains the commands as a list of ``CommandWithParties`` instances. ``CommandWithParties`` pairs
|
|
||||||
a ``CommandData`` with a list of required signers for the transaction:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 6
|
|
||||||
:end-before: DOCEND 6
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
* ``signers`` is the list of each signer's ``PublicKey``
|
|
||||||
* ``signingParties`` is the list of the signer's identities, if known
|
|
||||||
* ``value`` is the object being signed (a command, in this case)
|
|
||||||
|
|
||||||
Branching verify with commands
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
Generally, we will want to impose different constraints on a transaction based on its commands. For example, we will
|
|
||||||
want to impose different constraints on a cash issuance transaction to on a cash transfer transaction.
|
|
||||||
|
|
||||||
We can achieve this by extracting the command and using standard branching logic within ``verify``. Here, we extract
|
|
||||||
the single command of type ``XContract.Commands`` from the transaction, and branch ``verify`` accordingly:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
class XContract : Contract {
|
|
||||||
interface Commands : CommandData {
|
|
||||||
class Issue : TypeOnlyCommandData(), Commands
|
|
||||||
class Transfer : TypeOnlyCommandData(), Commands
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun verify(tx: LedgerTransaction) {
|
|
||||||
val command = tx.findCommand<Commands> { true }
|
|
||||||
|
|
||||||
when (command.value) {
|
|
||||||
is Commands.Issue -> {
|
|
||||||
// Issuance verification logic.
|
|
||||||
}
|
|
||||||
is Commands.Transfer -> {
|
|
||||||
// Transfer verification logic.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
public class XContract implements Contract {
|
|
||||||
public interface Commands extends CommandData {
|
|
||||||
class Issue extends TypeOnlyCommandData implements Commands {}
|
|
||||||
class Transfer extends TypeOnlyCommandData implements Commands {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verify(LedgerTransaction tx) {
|
|
||||||
final Commands command = tx.findCommand(Commands.class, cmd -> true).getValue();
|
|
||||||
|
|
||||||
if (command instanceof Commands.Issue) {
|
|
||||||
// Issuance verification logic.
|
|
||||||
} else if (command instanceof Commands.Transfer) {
|
|
||||||
// Transfer verification logic.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Core types
|
|
||||||
===============
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Corda provides several more core classes as part of its API.
|
|
||||||
|
|
||||||
SecureHash
|
|
||||||
----------
|
|
||||||
The ``SecureHash`` class is used to uniquely identify objects such as transactions and attachments by their hash.
|
|
||||||
Any object that needs to be identified by its hash should implement the ``NamedByHash`` interface:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
``SecureHash`` is a sealed class that only defines a single subclass, ``SecureHash.SHA256``. There are utility methods
|
|
||||||
to create and parse ``SecureHash.SHA256`` objects.
|
|
||||||
|
|
||||||
.. _composite_keys:
|
|
||||||
|
|
||||||
CompositeKey
|
|
||||||
------------
|
|
||||||
Corda supports scenarios where more than one signature is required to authorise a state object transition. For example:
|
|
||||||
"Either the CEO or 3 out of 5 of his assistants need to provide signatures".
|
|
||||||
|
|
||||||
This is achieved using a ``CompositeKey``, which uses public-key composition to organise the various public keys into a
|
|
||||||
tree data structure. A ``CompositeKey`` is a tree that stores the cryptographic public key primitives in its leaves and
|
|
||||||
the composition logic in the intermediary nodes. Every intermediary node specifies a *threshold* of how many child
|
|
||||||
signatures it requires.
|
|
||||||
|
|
||||||
An illustration of an *"either Alice and Bob, or Charlie"* composite key:
|
|
||||||
|
|
||||||
.. image:: resources/composite-key.png
|
|
||||||
:align: center
|
|
||||||
:width: 300px
|
|
||||||
|
|
||||||
To allow further flexibility, each child node can have an associated custom *weight* (the default is 1). The *threshold*
|
|
||||||
then specifies the minimum total weight of all children required. Our previous example can also be expressed as:
|
|
||||||
|
|
||||||
.. image:: resources/composite-key-2.png
|
|
||||||
:align: center
|
|
||||||
:width: 300px
|
|
||||||
|
|
||||||
Signature verification is performed in two stages:
|
|
||||||
|
|
||||||
1. Given a list of signatures, each signature is verified against the expected content.
|
|
||||||
2. The public keys corresponding to the signatures are matched against the leaves of the composite key tree in question,
|
|
||||||
and the total combined weight of all children is calculated for every intermediary node. If all thresholds are satisfied,
|
|
||||||
the composite key requirement is considered to be met.
|
|
File diff suppressed because it is too large
Load Diff
@ -1,155 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Identity
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Party
|
|
||||||
-----
|
|
||||||
Parties on the network are represented using the ``AbstractParty`` class. There are two types of ``AbstractParty``:
|
|
||||||
|
|
||||||
* ``Party``, identified by a ``PublicKey`` and a ``CordaX500Name``
|
|
||||||
* ``AnonymousParty``, identified by a ``PublicKey`` only
|
|
||||||
|
|
||||||
Using ``AnonymousParty`` to identify parties in states and commands prevents nodes from learning the identities
|
|
||||||
of the parties involved in a transaction when they verify the transaction's dependency chain. When preserving the
|
|
||||||
anonymity of each party is not required (e.g. for internal processing), ``Party`` can be used instead.
|
|
||||||
|
|
||||||
The identity service allows flows to resolve ``AnonymousParty`` to ``Party``, but only if the anonymous party's
|
|
||||||
identity has already been registered with the node (typically handled by ``SwapIdentitiesFlow`` or
|
|
||||||
``IdentitySyncFlow``, discussed below).
|
|
||||||
|
|
||||||
Party names use the ``CordaX500Name`` data class, which enforces the structure of names within Corda, as well as
|
|
||||||
ensuring a consistent rendering of the names in plain text.
|
|
||||||
|
|
||||||
Support for both ``Party`` and ``AnonymousParty`` classes in Corda enables sophisticated selective disclosure of
|
|
||||||
identity information. For example, it is possible to construct a transaction using an ``AnonymousParty`` (so nobody can
|
|
||||||
learn of your involvement by inspection of the transaction), yet prove to specific counterparts that this
|
|
||||||
``AnonymousParty`` actually corresponds to your well-known identity. This is achieved using the
|
|
||||||
``PartyAndCertificate`` data class, which contains the X.509 certificate path proving that a given ``AnonymousParty``
|
|
||||||
corresponds to a given ``Party``. Each ``PartyAndCertificate`` can be propagated to counterparties on a need-to-know
|
|
||||||
basis.
|
|
||||||
|
|
||||||
The ``PartyAndCertificate`` class is also used by the network map service to represent well-known identities, with the
|
|
||||||
certificate path proving the certificate was issued by the doorman service.
|
|
||||||
|
|
||||||
.. _confidential_identities_ref:
|
|
||||||
|
|
||||||
Confidential identities
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. warning:: The ``confidential-identities`` module is still not stabilised, so this API may change in future releases.
|
|
||||||
See :doc:`api-stability-guarantees`.
|
|
||||||
|
|
||||||
Confidential identities are key pairs where the corresponding X.509 certificate (and path) are not made public, so that
|
|
||||||
parties who are not involved in the transaction cannot identify the owner. They are owned by a well-known identity,
|
|
||||||
which must sign the X.509 certificate. Before constructing a new transaction the involved parties must generate and
|
|
||||||
exchange new confidential identities, a process which is managed using ``SwapIdentitiesFlow`` (discussed below). The
|
|
||||||
public keys of these confidential identities are then used when generating output states and commands for the
|
|
||||||
transaction.
|
|
||||||
|
|
||||||
Where using outputs from a previous transaction in a new transaction, counterparties may need to know who the involved
|
|
||||||
parties are. One example is the ``TwoPartyTradeFlow``, where an existing asset is exchanged for cash. If confidential
|
|
||||||
identities are being used, the buyer will want to ensure that the asset being transferred is owned by the seller, and
|
|
||||||
the seller will likewise want to ensure that the cash being transferred is owned by the buyer. Verifying this requires
|
|
||||||
both nodes to have a copy of the confidential identities for the asset and cash input states. ``IdentitySyncFlow``
|
|
||||||
manages this process. It takes as inputs a transaction and a counterparty, and for every confidential identity involved
|
|
||||||
in that transaction for which the calling node holds the certificate path, it sends this certificate path to the
|
|
||||||
counterparty.
|
|
||||||
|
|
||||||
SwapIdentitiesFlow
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
``SwapIdentitiesFlow`` is typically run as a subflow of another flow. It takes as its sole constructor argument the
|
|
||||||
counterparty we want to exchange confidential identities with. It returns a mapping from the identities of the caller
|
|
||||||
and the counterparty to their new confidential identities. In the future, this flow will be extended to handle swapping
|
|
||||||
identities with multiple parties at once.
|
|
||||||
|
|
||||||
You can see an example of using ``SwapIdentitiesFlow`` in ``TwoPartyDealFlow.kt``:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../finance/workflows/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 2
|
|
||||||
:end-before: DOCEND 2
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
``SwapIdentitiesFlow`` goes through the following key steps:
|
|
||||||
|
|
||||||
1. Generate a new confidential identity from our well-known identity
|
|
||||||
2. Create a ``CertificateOwnershipAssertion`` object containing the new confidential identity (X500 name, public key)
|
|
||||||
3. Sign this object with the confidential identity's private key
|
|
||||||
4. Send the confidential identity and aforementioned signature to counterparties, while receiving theirs
|
|
||||||
5. Verify the signatures to ensure that identities were generated by the involved set of parties
|
|
||||||
6. Verify the confidential identities are owned by the expected well known identities
|
|
||||||
7. Store the confidential identities and return them to the calling flow
|
|
||||||
|
|
||||||
This ensures not only that the confidential identity X.509 certificates are signed by the correct well-known
|
|
||||||
identities, but also that the confidential identity private key is held by the counterparty, and that a party cannot
|
|
||||||
claim ownership of another party's confidential identities.
|
|
||||||
|
|
||||||
IdentitySyncFlow
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
When constructing a transaction whose input states reference confidential identities, it is common for counterparties
|
|
||||||
to require knowledge of which well-known identity each confidential identity maps to. ``IdentitySyncFlow`` handles this
|
|
||||||
process. You can see an example of its use in ``TwoPartyTradeFlow.kt``.
|
|
||||||
|
|
||||||
``IdentitySyncFlow`` is divided into two parts:
|
|
||||||
|
|
||||||
* ``IdentitySyncFlow.Send``
|
|
||||||
* ``IdentitySyncFlow.Receive``
|
|
||||||
|
|
||||||
``IdentitySyncFlow.Send`` is invoked by the party initiating the identity synchronization:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../finance/workflows/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 6
|
|
||||||
:end-before: DOCEND 6
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
The identity synchronization flow goes through the following key steps:
|
|
||||||
|
|
||||||
1. Extract participant identities from all input and output states and remove any well known identities. Required
|
|
||||||
signers on commands are currently ignored as they are presumed to be included in the participants on states, or to
|
|
||||||
be well-known identities of services (such as an oracle service)
|
|
||||||
2. For each counterparty node, send a list of the public keys of the confidential identities, and receive back a list
|
|
||||||
of those the counterparty needs the certificate path for
|
|
||||||
3. Verify the requested list of identities contains only confidential identities in the offered list, and abort
|
|
||||||
otherwise
|
|
||||||
4. Send the requested confidential identities as ``PartyAndCertificate`` instances to the counterparty
|
|
||||||
|
|
||||||
.. note:: ``IdentitySyncFlow`` works on a push basis. The initiating node can only send confidential identities it has
|
|
||||||
the X.509 certificates for, and the remote nodes can only request confidential identities being offered (are
|
|
||||||
referenced in the transaction passed to the initiating flow). There is no standard flow for nodes to collect
|
|
||||||
confidential identities before assembling a transaction, and this is left for individual flows to manage if
|
|
||||||
required.
|
|
||||||
|
|
||||||
Meanwhile, ``IdentitySyncFlow.Receive`` is invoked by all the other (non-initiating) parties involved in the identity
|
|
||||||
synchronization process:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../finance/workflows/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 07
|
|
||||||
:end-before: DOCEND 07
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
``IdentitySyncFlow`` will serve all confidential identities in the provided transaction, irrespective of well-known
|
|
||||||
identity. This is important for more complex transaction cases with 3+ parties, for example:
|
|
||||||
|
|
||||||
* Alice is building the transaction, and provides some input state *x* owned by a confidential identity of Alice
|
|
||||||
* Bob provides some input state *y* owned by a confidential identity of Bob
|
|
||||||
* Charlie provides some input state *z* owned by a confidential identity of Charlie
|
|
||||||
|
|
||||||
Alice may know all of the confidential identities ahead of time, but Bob not know about Charlie's and vice-versa.
|
|
||||||
The assembled transaction therefore has three input states *x*, *y* and *z*, for which only Alice possesses
|
|
||||||
certificates for all confidential identities. ``IdentitySyncFlow`` must send not just Alice's confidential identity but
|
|
||||||
also any other identities in the transaction to the Bob and Charlie.
|
|
@ -1,467 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Persistence
|
|
||||||
================
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Corda offers developers the option to expose all or some parts of a contract state to an *Object Relational Mapping*
|
|
||||||
(ORM) tool to be persisted in a *Relational Database Management System* (RDBMS).
|
|
||||||
|
|
||||||
The purpose of this, is to assist :doc:`key-concepts-vault`
|
|
||||||
development and allow for the persistence of state data to a custom database table. Persisted states held in the
|
|
||||||
vault are indexed for the purposes of executing queries. This also allows for relational joins between Corda tables
|
|
||||||
and the organization's existing data.
|
|
||||||
|
|
||||||
The Object Relational Mapping is specified using `Java Persistence API <https://en.wikipedia.org/wiki/Java_Persistence_API>`_
|
|
||||||
(JPA) annotations. This mapping is persisted to the database as a table row (a single, implicitly structured data item) by the node
|
|
||||||
automatically every time a state is recorded in the node's local vault as part of a transaction.
|
|
||||||
|
|
||||||
.. note:: By default, nodes use an H2 database which is accessed using *Java Database Connectivity* JDBC. Any database
|
|
||||||
with a JDBC driver is a candidate and several integrations have been contributed to by the community.
|
|
||||||
Please see the info in ":doc:`node-database`" for details.
|
|
||||||
|
|
||||||
Schemas
|
|
||||||
-------
|
|
||||||
Every ``ContractState`` may implement the ``QueryableState`` interface if it wishes to be inserted into a custom table in the node's
|
|
||||||
database and made accessible using SQL.
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART QueryableState
|
|
||||||
:end-before: DOCEND QueryableState
|
|
||||||
|
|
||||||
The ``QueryableState`` interface requires the state to enumerate the different relational schemas it supports, for
|
|
||||||
instance in situations where the schema has evolved. Each relational schema is represented as a ``MappedSchema``
|
|
||||||
object returned by the state's ``supportedSchemas`` method.
|
|
||||||
|
|
||||||
Nodes have an internal ``SchemaService`` which decides what data to persist by selecting the ``MappedSchema`` to use.
|
|
||||||
Once a ``MappedSchema`` is selected, the ``SchemaService`` will delegate to the ``QueryableState`` to generate a corresponding
|
|
||||||
representation (mapped object) via the ``generateMappedObject`` method, the output of which is then passed to the *ORM*.
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/main/kotlin/net/corda/node/services/api/SchemaService.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART SchemaService
|
|
||||||
:end-before: DOCEND SchemaService
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART MappedSchema
|
|
||||||
:end-before: DOCEND MappedSchema
|
|
||||||
|
|
||||||
With this framework, the relational view of ledger states can evolve in a controlled fashion in lock-step with internal systems or other
|
|
||||||
integration points and is not dependant on changes to the contract code.
|
|
||||||
|
|
||||||
It is expected that multiple contract state implementations might provide mappings within a single schema.
|
|
||||||
For example an Interest Rate Swap contract and an Equity OTC Option contract might both provide a mapping to
|
|
||||||
a Derivative contract within the same schema. The schemas should typically not be part of the contract itself and should exist independently
|
|
||||||
to encourage re-use of a common set within a particular business area or CorDapp.
|
|
||||||
|
|
||||||
.. note:: It's advisable to avoid cross-references between different schemas as this may cause issues when evolving ``MappedSchema``
|
|
||||||
or migrating its data. At startup, nodes log such violations as warnings stating that there's a cross-reference between ``MappedSchema``'s.
|
|
||||||
The detailed messages incorporate information about what schemas, entities and fields are involved.
|
|
||||||
|
|
||||||
``MappedSchema`` offer a family name that is disambiguated using Java package style name-spacing derived from the
|
|
||||||
class name of a *schema family* class that is constant across versions, allowing the ``SchemaService`` to select a
|
|
||||||
preferred version of a schema.
|
|
||||||
|
|
||||||
The ``SchemaService`` is also responsible for the ``SchemaOptions`` that can be configured for a particular
|
|
||||||
``MappedSchema``. These allow the configuration of database schemas or table name prefixes to avoid clashes with
|
|
||||||
other ``MappedSchema``.
|
|
||||||
|
|
||||||
.. note:: It is intended that there should be plugin support for the ``SchemaService`` to offer version upgrading, implementation
|
|
||||||
of additional schemas, and enable active schemas as being configurable. The present implementation does not include these features
|
|
||||||
and simply results in all versions of all schemas supported by a ``QueryableState`` being persisted.
|
|
||||||
This will change in due course. Similarly, the service does not currently support
|
|
||||||
configuring ``SchemaOptions`` but will do so in the future.
|
|
||||||
|
|
||||||
Custom schema registration
|
|
||||||
--------------------------
|
|
||||||
Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan for states that implement
|
|
||||||
the Queryable state interface. Tables are then created as specified by the ``MappedSchema`` identified by each state's ``supportedSchemas`` method.
|
|
||||||
|
|
||||||
For testing purposes it is necessary to manually register the packages containing custom schemas as follows:
|
|
||||||
|
|
||||||
- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register packages using the `cordappPackages` parameter of ``MockNetwork``
|
|
||||||
- Tests using ``MockServices`` must explicitly register packages using the `cordappPackages` parameter of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method.
|
|
||||||
|
|
||||||
.. note:: Tests using the `DriverDSL` will automatically register your custom schemas if they are in the same project structure as the driver call.
|
|
||||||
|
|
||||||
Object relational mapping
|
|
||||||
-------------------------
|
|
||||||
To facilitate the ORM, the persisted representation of a ``QueryableState`` should be an instance of a ``PersistentState`` subclass,
|
|
||||||
constructed either by the state itself or a plugin to the ``SchemaService``. This allows the ORM layer to always
|
|
||||||
associate a ``StateRef`` with a persisted representation of a ``ContractState`` and allows joining with the set of
|
|
||||||
unconsumed states in the vault.
|
|
||||||
|
|
||||||
The ``PersistentState`` subclass should be marked up as a JPA 2.1 *Entity* with a defined table name and having
|
|
||||||
properties (in Kotlin, getters/setters in Java) annotated to map to the appropriate columns and SQL types. Additional
|
|
||||||
entities can be included to model these properties where they are more complex, for example collections (:ref:`Persisting Hierarchical Data<persisting-hierarchical-data>`), so
|
|
||||||
the mapping does not have to be *flat*. The ``MappedSchema`` constructor accepts a list of all JPA entity classes for that schema in
|
|
||||||
the ``MappedTypes`` parameter. It must provide this list in order to initialise the ORM layer.
|
|
||||||
|
|
||||||
Several examples of entities and mappings are provided in the codebase, including ``Cash.State`` and
|
|
||||||
``CommercialPaper.State``. For example, here's the first version of the cash schema.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../finance/contracts/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt
|
|
||||||
:language: kotlin
|
|
||||||
|
|
||||||
.. note:: If Cordapp needs to be portable between Corda OS (running against H2) and Corda Enterprise (running against a standalone database),
|
|
||||||
consider database vendors specific requirements.
|
|
||||||
Ensure that table and column names are compatible with the naming convention of the database vendors for which the Cordapp will be deployed,
|
|
||||||
e.g. for Oracle database, prior to version 12.2 the maximum length of table/column name is 30 bytes (the exact number of characters depends on the database encoding).
|
|
||||||
|
|
||||||
Persisting Hierarchical Data
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
You may wish to persist hierarchical relationships within states using multiple database tables
|
|
||||||
|
|
||||||
You may wish to persist hierarchical relationships within state data using multiple database tables. In order to facillitate this, multiple ``PersistentState``
|
|
||||||
subclasses may be implemented. The relationship between these classes is defined using JPA annotations. It is important to note that the ``MappedSchema``
|
|
||||||
constructor requires a list of *all* of these subclasses.
|
|
||||||
|
|
||||||
An example Schema implementing hierarchical relationships with JPA annotations has been implemented below. This Schema will cause ``parent_data`` and ``child_data`` tables to be
|
|
||||||
created.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
@CordaSerializable
|
|
||||||
public class SchemaV1 extends MappedSchema {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class must extend the MappedSchema class. Its name is based on the SchemaFamily name and the associated version number abbreviation (V1, V2... Vn).
|
|
||||||
* In the constructor, use the super keyword to call the constructor of MappedSchema with the following arguments: a class literal representing the schema family,
|
|
||||||
* a version number and a collection of mappedTypes (class literals) which represent JPA entity classes that the ORM layer needs to be configured with for this schema.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public SchemaV1() {
|
|
||||||
super(Schema.class, 1, ImmutableList.of(PersistentParentToken.class, PersistentChildToken.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The @entity annotation signifies that the specified POJO class' non-transient fields should be persisted to a relational database using the services
|
|
||||||
* of an entity manager. The @table annotation specifies properties of the table that will be created to contain the persisted data, in this case we have
|
|
||||||
* specified a name argument which will be used the table's title.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "parent_data")
|
|
||||||
public static class PersistentParentToken extends PersistentState {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The @Column annotations specify the columns that will comprise the inserted table and specify the shape of the fields and associated
|
|
||||||
* data types of each database entry.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Column(name = "owner") private final String owner;
|
|
||||||
@Column(name = "issuer") private final String issuer;
|
|
||||||
@Column(name = "amount") private final int amount;
|
|
||||||
@Column(name = "linear_id") public final UUID linearId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The @OneToMany annotation specifies a one-to-many relationship between this class and a collection included as a field.
|
|
||||||
* The @JoinColumn and @JoinColumns annotations specify on which columns these tables will be joined on.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.PERSIST)
|
|
||||||
@JoinColumns({
|
|
||||||
@JoinColumn(name = "output_index", referencedColumnName = "output_index"),
|
|
||||||
@JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"),
|
|
||||||
})
|
|
||||||
private final List<PersistentChildToken> listOfPersistentChildTokens;
|
|
||||||
|
|
||||||
public PersistentParentToken(String owner, String issuer, int amount, UUID linearId, List<PersistentChildToken> listOfPersistentChildTokens) {
|
|
||||||
this.owner = owner;
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.amount = amount;
|
|
||||||
this.linearId = linearId;
|
|
||||||
this.listOfPersistentChildTokens = listOfPersistentChildTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default constructor required by hibernate.
|
|
||||||
public PersistentParentToken() {
|
|
||||||
this.owner = "";
|
|
||||||
this.issuer = "";
|
|
||||||
this.amount = 0;
|
|
||||||
this.linearId = UUID.randomUUID();
|
|
||||||
this.listOfPersistentChildTokens = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOwner() {
|
|
||||||
return owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIssuer() {
|
|
||||||
return issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getLinearId() {
|
|
||||||
return linearId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PersistentChildToken> getChildTokens() { return listOfPersistentChildTokens; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@CordaSerializable
|
|
||||||
@Table(name = "child_data")
|
|
||||||
public static class PersistentChildToken {
|
|
||||||
// The @Id annotation marks this field as the primary key of the persisted entity.
|
|
||||||
@Id
|
|
||||||
private final UUID Id;
|
|
||||||
@Column(name = "owner")
|
|
||||||
private final String owner;
|
|
||||||
@Column(name = "issuer")
|
|
||||||
private final String issuer;
|
|
||||||
@Column(name = "amount")
|
|
||||||
private final int amount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The @ManyToOne annotation specifies that this class will be present as a member of a collection on a parent class and that it should
|
|
||||||
* be persisted with the joining columns specified in the parent class. It is important to note the targetEntity parameter which should correspond
|
|
||||||
* to a class literal of the parent class.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@ManyToOne(targetEntity = PersistentParentToken.class)
|
|
||||||
private final TokenState persistentParentToken;
|
|
||||||
|
|
||||||
|
|
||||||
public PersistentChildToken(String owner, String issuer, int amount) {
|
|
||||||
this.Id = UUID.randomUUID();
|
|
||||||
this.owner = owner;
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.amount = amount;
|
|
||||||
this.persistentParentToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default constructor required by hibernate.
|
|
||||||
public PersistentChildToken() {
|
|
||||||
this.Id = UUID.randomUUID();
|
|
||||||
this.owner = "";
|
|
||||||
this.issuer = "";
|
|
||||||
this.amount = 0;
|
|
||||||
this.persistentParentToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOwner() {
|
|
||||||
return owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIssuer() {
|
|
||||||
return issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TokenState getPersistentToken() {
|
|
||||||
return persistentToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
@CordaSerializable
|
|
||||||
object SchemaV1 : MappedSchema(schemaFamily = Schema::class.java, version = 1, mappedTypes = listOf(PersistentParentToken::class.java, PersistentChildToken::class.java)) {
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "parent_data")
|
|
||||||
class PersistentParentToken(
|
|
||||||
@Column(name = "owner")
|
|
||||||
var owner: String,
|
|
||||||
|
|
||||||
@Column(name = "issuer")
|
|
||||||
var issuer: String,
|
|
||||||
|
|
||||||
@Column(name = "amount")
|
|
||||||
var currency: Int,
|
|
||||||
|
|
||||||
@Column(name = "linear_id")
|
|
||||||
var linear_id: UUID,
|
|
||||||
|
|
||||||
@JoinColumns(JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"), JoinColumn(name = "output_index", referencedColumnName = "output_index"))
|
|
||||||
|
|
||||||
var listOfPersistentChildTokens: MutableList<PersistentChildToken>
|
|
||||||
) : PersistentState()
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@CordaSerializable
|
|
||||||
@Table(name = "child_data")
|
|
||||||
class PersistentChildToken(
|
|
||||||
@Id
|
|
||||||
var Id: UUID = UUID.randomUUID(),
|
|
||||||
|
|
||||||
@Column(name = "owner")
|
|
||||||
var owner: String,
|
|
||||||
|
|
||||||
@Column(name = "issuer")
|
|
||||||
var issuer: String,
|
|
||||||
|
|
||||||
@Column(name = "amount")
|
|
||||||
var currency: Int,
|
|
||||||
|
|
||||||
@Column(name = "linear_id")
|
|
||||||
var linear_id: UUID,
|
|
||||||
|
|
||||||
@ManyToOne(targetEntity = PersistentParentToken::class)
|
|
||||||
var persistentParentToken: TokenState
|
|
||||||
|
|
||||||
) : PersistentState()
|
|
||||||
|
|
||||||
|
|
||||||
Identity mapping
|
|
||||||
----------------
|
|
||||||
Schema entity attributes defined by identity types (``AbstractParty``, ``Party``, ``AnonymousParty``) are automatically
|
|
||||||
processed to ensure only the ``X500Name`` of the identity is persisted where an identity is well known, otherwise a null
|
|
||||||
value is stored in the associated column. To preserve privacy, identity keys are never persisted. Developers should use
|
|
||||||
the ``IdentityService`` to resolve keys from well know X500 identity names.
|
|
||||||
|
|
||||||
.. _jdbc_session_ref:
|
|
||||||
|
|
||||||
JDBC session
|
|
||||||
------------
|
|
||||||
Apps may also interact directly with the underlying Node's database by using a standard
|
|
||||||
JDBC connection (session) as described by the `Java SQL Connection API <https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html>`_
|
|
||||||
|
|
||||||
Use the ``ServiceHub`` ``jdbcSession`` function to obtain a JDBC connection as illustrated in the following example:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART JdbcSession
|
|
||||||
:end-before: DOCEND JdbcSession
|
|
||||||
|
|
||||||
JDBC sessions can be used in flows and services (see ":doc:`flow-state-machines`").
|
|
||||||
|
|
||||||
The following example illustrates the creation of a custom Corda service using a ``jdbcSession``:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/vault/CustomVaultQuery.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART CustomVaultQuery
|
|
||||||
:end-before: DOCEND CustomVaultQuery
|
|
||||||
|
|
||||||
which is then referenced within a custom flow:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/vault/CustomVaultQuery.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART TopupIssuer
|
|
||||||
:end-before: DOCEND TopupIssuer
|
|
||||||
|
|
||||||
For examples on testing ``@CordaService`` implementations, see the oracle example :doc:`here <oracles>`.
|
|
||||||
|
|
||||||
JPA Support
|
|
||||||
-----------
|
|
||||||
In addition to ``jdbcSession``, ``ServiceHub`` also exposes the Java Persistence API to flows via the ``withEntityManager``
|
|
||||||
method. This method can be used to persist and query entities which inherit from ``MappedSchema``. This is particularly
|
|
||||||
useful if off-ledger data must be maintained in conjunction with on-ledger state data.
|
|
||||||
|
|
||||||
.. note:: Your entity must be included as a mappedType as part of a ``MappedSchema`` for it to be added to Hibernate
|
|
||||||
as a custom schema. If it's not included as a mappedType, a corresponding table will not be created. See Samples below.
|
|
||||||
|
|
||||||
The code snippet below defines a ``PersistentFoo`` type inside ``FooSchemaV1``. Note that ``PersistentFoo`` is added to
|
|
||||||
a list of mapped types which is passed to ``MappedSchema``. This is exactly how state schemas are defined, except that
|
|
||||||
the entity in this case should not subclass ``PersistentState`` (as it is not a state object). See examples:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
public class FooSchema {}
|
|
||||||
|
|
||||||
public class FooSchemaV1 extends MappedSchema {
|
|
||||||
FooSchemaV1() {
|
|
||||||
super(FooSchema.class, 1, ImmutableList.of(PersistentFoo.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "foos")
|
|
||||||
class PersistentFoo implements Serializable {
|
|
||||||
@Id
|
|
||||||
@Column(name = "foo_id")
|
|
||||||
String fooId;
|
|
||||||
|
|
||||||
@Column(name = "foo_data")
|
|
||||||
String fooData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
object FooSchema
|
|
||||||
|
|
||||||
object FooSchemaV1 : MappedSchema(schemaFamily = FooSchema.javaClass, version = 1, mappedTypes = listOf(PersistentFoo::class.java)) {
|
|
||||||
@Entity
|
|
||||||
@Table(name = "foos")
|
|
||||||
class PersistentFoo(@Id @Column(name = "foo_id") var fooId: String, @Column(name = "foo_data") var fooData: String) : Serializable
|
|
||||||
}
|
|
||||||
|
|
||||||
Instances of ``PersistentFoo`` can be manually persisted inside a flow as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
PersistentFoo foo = new PersistentFoo(new UniqueIdentifier().getId().toString(), "Bar");
|
|
||||||
serviceHub.withEntityManager(entityManager -> {
|
|
||||||
entityManager.persist(foo);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val foo = FooSchemaV1.PersistentFoo(UniqueIdentifier().id.toString(), "Bar")
|
|
||||||
serviceHub.withEntityManager {
|
|
||||||
persist(foo)
|
|
||||||
}
|
|
||||||
|
|
||||||
And retrieved via a query, as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
node.getServices().withEntityManager((EntityManager entityManager) -> {
|
|
||||||
CriteriaQuery<PersistentFoo> query = entityManager.getCriteriaBuilder().createQuery(PersistentFoo.class);
|
|
||||||
Root<PersistentFoo> type = query.from(PersistentFoo.class);
|
|
||||||
query.select(type);
|
|
||||||
return entityManager.createQuery(query).getResultList();
|
|
||||||
});
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val result: MutableList<FooSchemaV1.PersistentFoo> = services.withEntityManager {
|
|
||||||
val query = criteriaBuilder.createQuery(FooSchemaV1.PersistentFoo::class.java)
|
|
||||||
val type = query.from(FooSchemaV1.PersistentFoo::class.java)
|
|
||||||
query.select(type)
|
|
||||||
createQuery(query).resultList
|
|
||||||
}
|
|
||||||
|
|
||||||
Please note that suspendable flow operations such as:
|
|
||||||
|
|
||||||
* ``FlowSession.send``
|
|
||||||
* ``FlowSession.receive``
|
|
||||||
* ``FlowLogic.receiveAll``
|
|
||||||
* ``FlowLogic.sendAll``
|
|
||||||
* ``FlowLogic.sleep``
|
|
||||||
* ``FlowLogic.subFlow``
|
|
||||||
|
|
||||||
Cannot be used within the lambda function passed to ``withEntityManager``.
|
|
@ -1,29 +0,0 @@
|
|||||||
API: RPC operations
|
|
||||||
===================
|
|
||||||
The node's owner interacts with the node solely via remote procedure calls (RPC). The node's owner does not have
|
|
||||||
access to the node's ``ServiceHub``.
|
|
||||||
|
|
||||||
The key RPC operations exposed by the node are:
|
|
||||||
|
|
||||||
* ``CordaRPCOps.vaultQueryBy``
|
|
||||||
* Extract states from the node's vault based on a query criteria
|
|
||||||
* ``CordaRPCOps.vaultTrackBy``
|
|
||||||
* As above, but also returns an observable of future states matching the query
|
|
||||||
* ``CordaRPCOps.networkMapFeed``
|
|
||||||
* A list of network nodes, and an observable of changes to the network map
|
|
||||||
* ``CordaRPCOps.registeredFlows``
|
|
||||||
* See a list of registered flows on the node
|
|
||||||
* ``CordaRPCOps.startFlowDynamic``
|
|
||||||
* Start one of the node's registered flows
|
|
||||||
* ``CordaRPCOps.startTrackedFlowDynamic``
|
|
||||||
* As above, but also returns a progress handle for the flow
|
|
||||||
* ``CordaRPCOps.nodeDiagnosticInfo``
|
|
||||||
* Returns diagnostic information about the node, including the version and CorDapp details
|
|
||||||
* ``CordaRPCOps.nodeInfo``
|
|
||||||
* Returns the network map entry of the node, including its address and identity details as well as the platform version information
|
|
||||||
* ``CordaRPCOps.currentNodeTime``
|
|
||||||
* Returns the current time according to the node's clock
|
|
||||||
* ``CordaRPCOps.partyFromKey/CordaRPCOps.wellKnownPartyFromX500Name``
|
|
||||||
* Retrieves a party on the network based on a public key or X500 name
|
|
||||||
* ``CordaRPCOps.uploadAttachment``/``CordaRPCOps.openAttachment``/``CordaRPCOps.attachmentExists``
|
|
||||||
* Uploads, opens and checks for the existence of attachments
|
|
@ -1,58 +0,0 @@
|
|||||||
Checking API stability
|
|
||||||
======================
|
|
||||||
|
|
||||||
We have committed not to alter Corda's API so that developers will not have to keep rewriting their CorDapps with each
|
|
||||||
new Corda release. The stable Corda modules are listed :ref:`here <internal-apis-and-stability-guarantees>`. Our CI process runs an "API Stability"
|
|
||||||
check for each GitHub pull request in order to check that we don't accidentally introduce an API-breaking change.
|
|
||||||
|
|
||||||
Build Process
|
|
||||||
-------------
|
|
||||||
|
|
||||||
As part of the build process the following commands are run for each PR:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ gradlew generateApi
|
|
||||||
$ .ci/check-api-changes.sh
|
|
||||||
|
|
||||||
This ``bash`` script has been tested on both MacOS and various Linux distributions, it can also be run on Windows with the
|
|
||||||
use of a suitable bash emulator such as git bash. The script's return value is the number of API-breaking changes that it
|
|
||||||
has detected, and this should be zero for the check to pass. The maximum return value is 255, although the script will still
|
|
||||||
correctly report higher numbers of breaking changes.
|
|
||||||
|
|
||||||
There are three kinds of breaking change:
|
|
||||||
|
|
||||||
* Removal or modification of existing API, i.e. an existing class, method or field has been either deleted or renamed, or
|
|
||||||
its signature somehow altered.
|
|
||||||
* Addition of a new method to an interface or abstract class. Types that have been annotated as ``@DoNotImplement`` are
|
|
||||||
excluded from this check. (This annotation is also inherited across subclasses and sub-interfaces.)
|
|
||||||
* Exposure of an internal type via a public API. Internal types are considered to be anything in a ``*.internal.`` package
|
|
||||||
or anything in a module that isn't in the stable modules list :ref:`here <internal-apis-and-stability-guarantees>`.
|
|
||||||
|
|
||||||
Developers can execute these commands themselves before submitting their PR, to ensure that they haven't inadvertently
|
|
||||||
broken Corda's API.
|
|
||||||
|
|
||||||
|
|
||||||
How it works
|
|
||||||
------------
|
|
||||||
|
|
||||||
The ``generateApi`` Gradle task writes a summary of Corda's public API into the file ``build/api/api-corda-|corda_version|.txt``.
|
|
||||||
The ``.ci/check-api-changes.sh`` script then compares this file with the contents of ``.ci/api-current.txt``, which is a
|
|
||||||
managed file within the Corda repository.
|
|
||||||
|
|
||||||
The Gradle task itself is implemented by the API Scanner plugin. More information on the API Scanner plugin is available `here <https://github.com/corda/corda-gradle-plugins/tree/master/api-scanner>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Updating the API
|
|
||||||
----------------
|
|
||||||
|
|
||||||
As a rule, ``api-current.txt`` should only be updated by the release manager for each Corda release.
|
|
||||||
|
|
||||||
We do not expect modifications to ``api-current.txt`` as part of normal development. However, we may sometimes need to adjust
|
|
||||||
the public API in ways that would not break developers' CorDapps but which would be blocked by the API Stability check.
|
|
||||||
For example, migrating a method from an interface into a superinterface. Any changes to the API summary file should be
|
|
||||||
included in the PR, which would then need explicit approval from either `Mike Hearn <https://github.com/mikehearn>`_, `Rick Parker <https://github.com/rick-r3>`_ or `Matthew Nesbit <https://github.com/mnesbit>`_.
|
|
||||||
|
|
||||||
.. note:: If you need to modify ``api-current.txt``, do not re-generate the file on the master branch. This will include new API that
|
|
||||||
hasn't been released or committed to, and may be subject to change. Manually change the specific line or lines of the
|
|
||||||
existing committed API that has changed.
|
|
@ -1,131 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Service Classes
|
|
||||||
====================
|
|
||||||
|
|
||||||
Service classes are long-lived instances that can trigger or be triggered by flows from within a node. A Service class is limited to a
|
|
||||||
single instance per node. During startup, the node handles the creation of the service. If there is problem when instantiating service
|
|
||||||
the node will report in the log what the problem was and terminate.
|
|
||||||
|
|
||||||
Services allow related, reusable, functions to be separated into their own class where their functionality is
|
|
||||||
grouped together. These functions can then be called from other services or flows.
|
|
||||||
|
|
||||||
Creating a Service
|
|
||||||
------------------
|
|
||||||
|
|
||||||
To define a Service class:
|
|
||||||
|
|
||||||
* Add the ``CordaService`` annotation
|
|
||||||
* Add a constructor with a single parameter of ``AppServiceHub``
|
|
||||||
* Extend ``SingletonSerializeAsToken``
|
|
||||||
|
|
||||||
Below is an empty implementation of a Service class:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
@CordaService
|
|
||||||
class MyCordaService(private val serviceHub: AppServiceHub) : SingletonSerializeAsToken() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Custom code ran at service creation
|
|
||||||
|
|
||||||
// Optional: Express interest in receiving lifecycle events
|
|
||||||
services.register { processEvent(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processEvent(event: ServiceLifecycleEvent) {
|
|
||||||
// Lifecycle event handling code including full use of serviceHub
|
|
||||||
when (event) {
|
|
||||||
STATE_MACHINE_STARTED -> {
|
|
||||||
services.vaultService.queryBy(...)
|
|
||||||
services.startFlow(...)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// Process other types of events
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// public api of service
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
@CordaService
|
|
||||||
public class MyCordaService extends SingletonSerializeAsToken {
|
|
||||||
|
|
||||||
private final AppServiceHub serviceHub;
|
|
||||||
|
|
||||||
public MyCordaService(AppServiceHub serviceHub) {
|
|
||||||
this.serviceHub = serviceHub;
|
|
||||||
// Custom code ran at service creation
|
|
||||||
|
|
||||||
// Optional: Express interest in receiving lifecycle events
|
|
||||||
serviceHub.register(SERVICE_PRIORITY_NORMAL, this::processEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processEvent(ServiceLifecycleEvent event) {
|
|
||||||
switch (event) {
|
|
||||||
case STATE_MACHINE_STARTED:
|
|
||||||
serviceHub.getVaultService().queryBy(...)
|
|
||||||
serviceHub.startFlow(...)
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Process other types of events
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// public api of service
|
|
||||||
}
|
|
||||||
|
|
||||||
The ``AppServiceHub`` provides the ``ServiceHub`` functionality to the Service class, with the extra ability to start flows. Starting flows
|
|
||||||
from ``AppServiceHub`` is explained further in :ref:`Starting Flows from a Service <starting_flows_from_a_service>`.
|
|
||||||
|
|
||||||
The ``AppServiceHub`` also provides access to ``database`` which will enable the Service class to perform DB transactions from the threads
|
|
||||||
managed by the Service.
|
|
||||||
|
|
||||||
Also the ``AppServiceHub`` provides ability for ``CordaService`` to subscribe for lifecycle events of the node, such that it will get notified
|
|
||||||
about node finishing initialisation and when the node is shutting down such that ``CordaService`` will be able to perform clean-up of some
|
|
||||||
critical resources. For more details please have refer to KDocs for ``ServiceLifecycleObserver``.
|
|
||||||
|
|
||||||
Retrieving a Service
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
A Service class can be retrieved by calling ``ServiceHub.cordaService`` which returns the single instance of the class passed into the function:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val service: MyCordaService = serviceHub.cordaService(MyCordaService::class.java)
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
MyCordaService service = serviceHub.cordaService(MyCordaService.class);
|
|
||||||
|
|
||||||
.. warning:: ``ServiceHub.cordaService`` should not be called during initialisation of a flow and should instead be called in line where
|
|
||||||
needed or set after the flow's ``call`` function has been triggered.
|
|
||||||
|
|
||||||
.. _starting_flows_from_a_service:
|
|
||||||
|
|
||||||
Starting Flows from a Service
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Starting flows via a service can lead to deadlock within the node's flow worker queue, which will prevent new flows from
|
|
||||||
starting. To avoid this, the rules bellow should be followed:
|
|
||||||
|
|
||||||
* When called from a running flow, the service must invoke the new flow from another thread. The existing flow cannot await the
|
|
||||||
execution of the new flow.
|
|
||||||
* When ``ServiceHub.trackBy`` is placed inside the service, flows started inside the observable must be placed onto another thread.
|
|
||||||
* Flows started by other means, do not require any special treatment.
|
|
||||||
|
|
||||||
.. note:: It is possible to avoid deadlock without following these rules depending on the number of flows running within the node. But, if the
|
|
||||||
number of flows violating these rules reaches the flow worker queue size, then the node will deadlock. It is best practice to
|
|
||||||
abide by these rules to remove this possibility.
|
|
@ -1,33 +0,0 @@
|
|||||||
API: ServiceHub
|
|
||||||
===============
|
|
||||||
Within ``FlowLogic.call``, the flow developer has access to the node's ``ServiceHub``, which provides access to the
|
|
||||||
various services the node provides. The services offered by the ``ServiceHub`` are split into the following categories:
|
|
||||||
|
|
||||||
* ``ServiceHub.networkMapCache``
|
|
||||||
* Provides information on other nodes on the network (e.g. notaries…)
|
|
||||||
* ``ServiceHub.identityService``
|
|
||||||
* Allows you to resolve anonymous identities to well-known identities if you have the required certificates
|
|
||||||
* ``ServiceHub.attachments``
|
|
||||||
* Gives you access to the node's attachments
|
|
||||||
* ``ServiceHub.validatedTransactions``
|
|
||||||
* Gives you access to the transactions stored in the node
|
|
||||||
* ``ServiceHub.vaultService``
|
|
||||||
* Stores the node’s current and historic states
|
|
||||||
* ``ServiceHub.keyManagementService``
|
|
||||||
* Manages signing transactions and generating fresh public keys
|
|
||||||
* ``ServiceHub.myInfo``
|
|
||||||
* Other information about the node
|
|
||||||
* ``ServiceHub.clock``
|
|
||||||
* Provides access to the node’s internal time and date
|
|
||||||
* ``ServiceHub.diagnosticsService``
|
|
||||||
* Provides diagnostic information about the node, including the node version and currently running apps. Note that this data should be
|
|
||||||
used for diagnostic purposes ONLY
|
|
||||||
* ``ServiceHub.contractUpgradeService``
|
|
||||||
* Provides functionality for secure contract upgrades
|
|
||||||
|
|
||||||
Additional, ``ServiceHub`` exposes the following properties:
|
|
||||||
|
|
||||||
* ``ServiceHub.loadState`` and ``ServiceHub.toStateAndRef`` to resolve a ``StateRef`` into a ``TransactionState`` or
|
|
||||||
a ``StateAndRef``
|
|
||||||
* ``ServiceHub.signInitialTransaction`` to sign a ``TransactionBuilder`` and convert it into a ``SignedTransaction``
|
|
||||||
* ``ServiceHub.createSignature`` and ``ServiceHub.addSignature`` to create and add signatures to a ``SignedTransaction``
|
|
@ -1,75 +0,0 @@
|
|||||||
.. _internal-apis-and-stability-guarantees:
|
|
||||||
|
|
||||||
API stability guarantees
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
Corda makes certain commitments about what parts of the API will preserve backwards compatibility as they change and
|
|
||||||
which will not. Over time, more of the API will fall under the stability guarantees. Thus, APIs can be categorized in the following 2 broad categories:
|
|
||||||
|
|
||||||
* **public APIs**, for which API/`ABI <https://en.wikipedia.org/wiki/Application_binary_interface>`_ backwards compatibility guarantees are provided. See: :ref:`public-api`
|
|
||||||
* **non-public APIs**, for which no backwards compatibility guarantees are provided. See: :ref:`non-public-api`
|
|
||||||
|
|
||||||
.. _public-api:
|
|
||||||
|
|
||||||
Public API
|
|
||||||
----------
|
|
||||||
|
|
||||||
The following modules form part of Corda's public API and we commit to API/ABI backwards compatibility in following releases, unless an incompatible change is required for security reasons:
|
|
||||||
|
|
||||||
* **Core (net.corda.core)**: core Corda libraries such as crypto functions, types for Corda's building blocks: states, contracts, transactions, attachments, etc. and some interfaces for nodes and protocols
|
|
||||||
* **Client RPC (net.corda.client.rpc)**: client RPC
|
|
||||||
* **Client Jackson (net.corda.client.jackson)**: JSON support for client applications
|
|
||||||
* **DSL Test Utils (net.corda.testing.dsl)**: a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
|
|
||||||
* **Test Node Driver (net.corda.testing.node, net.corda.testing.driver)**: test utilities to run nodes programmatically
|
|
||||||
* **Test Utils (net.corda.testing.core)**: generic test utilities
|
|
||||||
* **Http Test Utils (net.corda.testing.http)**: a small set of utilities for making HttpCalls, aimed at demos and tests.
|
|
||||||
* **Dummy Contracts (net.corda.testing.contracts)**: dummy state and contracts for testing purposes
|
|
||||||
* **Mock Services (net.corda.testing.services)**: mock service implementations for testing purposes
|
|
||||||
|
|
||||||
Additionally, the **Tokens SDK (com.r3.corda.lib.tokens)** available in `the Tokens GitHub repository <https://github.com/corda/token-sdk>`_
|
|
||||||
has a stable API.
|
|
||||||
|
|
||||||
.. _non-public-api:
|
|
||||||
|
|
||||||
Non-public API (experimental)
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The following are not part of the Corda's public API and no backwards compatibility guarantees are provided:
|
|
||||||
|
|
||||||
* Incubating modules, for which we will do our best to minimise disruption to developers using them until we are able to graduate them into the public API
|
|
||||||
* Internal modules, which are not to be used, and will change without notice
|
|
||||||
* Anything defined in a package containing ``.internal`` (for example, ``net.corda.core.internal`` and sub-packages should
|
|
||||||
not be used)
|
|
||||||
* Any interfaces, classes or methods whose name contains the word ``internal`` or ``Internal``
|
|
||||||
|
|
||||||
The **finance module** was the first CorDapp ever written and is a legacy module. Although it is not a part of our API guarantees, we also
|
|
||||||
don't anticipate much future change to it. Users should use the tokens SDK instead.
|
|
||||||
|
|
||||||
Corda incubating modules
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* **net.corda.confidential**: experimental support for confidential identities on the ledger
|
|
||||||
* **net.corda.client.jfx**: support for Java FX UI
|
|
||||||
* **net.corda.client.mock**: client mock utilities
|
|
||||||
* **Cordformation**: Gradle integration plugins
|
|
||||||
|
|
||||||
Corda internal modules
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Every other module is internal and will change without notice, even deleted, and should not be used.
|
|
||||||
|
|
||||||
Some of the public modules may depend on internal modules, so be careful to not rely on these transitive dependencies. In particular, the
|
|
||||||
testing modules depend on the node module and so you may end having the node in your test classpath.
|
|
||||||
|
|
||||||
.. warning:: The web server module will be removed in future. You should call Corda nodes through RPC from your web server of choice e.g., Spring Boot, Vertx, Undertow.
|
|
||||||
|
|
||||||
The ``@DoNotImplement`` annotation
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Certain interfaces and abstract classes within the Corda API have been annotated
|
|
||||||
as ``@DoNotImplement``. While we undertake not to remove or modify any of these classes' existing
|
|
||||||
functionality, the annotation is a warning that we may need to extend them in future versions of Corda.
|
|
||||||
Cordapp developers should therefore just use these classes "as is", and *not* attempt to extend or implement any of them themselves.
|
|
||||||
|
|
||||||
This annotation is inherited by subclasses and sub-interfaces.
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: States
|
|
||||||
===========
|
|
||||||
|
|
||||||
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-states`.
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
ContractState
|
|
||||||
-------------
|
|
||||||
In Corda, states are instances of classes that implement ``ContractState``. The ``ContractState`` interface is defined
|
|
||||||
as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/ContractState.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
``ContractState`` has a single field, ``participants``. ``participants`` is a ``List`` of the ``AbstractParty`` that
|
|
||||||
are considered to have a stake in the state. Among other things, the ``participants`` will:
|
|
||||||
|
|
||||||
* Usually store the state in their vault (see below)
|
|
||||||
|
|
||||||
* Need to sign any notary-change and contract-upgrade transactions involving this state
|
|
||||||
|
|
||||||
* Receive any finalised transactions involving this state as part of ``FinalityFlow`` / ``ReceiveFinalityFlow``
|
|
||||||
|
|
||||||
ContractState sub-interfaces
|
|
||||||
----------------------------
|
|
||||||
The behaviour of the state can be further customised by implementing sub-interfaces of ``ContractState``. The two most
|
|
||||||
common sub-interfaces are:
|
|
||||||
|
|
||||||
* ``LinearState``
|
|
||||||
|
|
||||||
* ``OwnableState``
|
|
||||||
|
|
||||||
``LinearState`` models shared facts for which there is only one current version at any point in time. ``LinearState``
|
|
||||||
states evolve in a straight line by superseding themselves. On the other hand, ``OwnableState`` is meant to represent
|
|
||||||
assets that can be freely split and merged over time. Cash is a good example of an ``OwnableState`` - two existing $5
|
|
||||||
cash states can be combined into a single $10 cash state, or split into five $1 cash states. With ``OwnableState``, its
|
|
||||||
the total amount held that is important, rather than the actual units held.
|
|
||||||
|
|
||||||
We can picture the hierarchy as follows:
|
|
||||||
|
|
||||||
.. image:: resources/state-hierarchy.png
|
|
||||||
|
|
||||||
LinearState
|
|
||||||
^^^^^^^^^^^
|
|
||||||
The ``LinearState`` interface is defined as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 2
|
|
||||||
:end-before: DOCEND 2
|
|
||||||
|
|
||||||
Remember that in Corda, states are immutable and can't be updated directly. Instead, we represent an evolving fact as a
|
|
||||||
sequence of ``LinearState`` states that share the same ``linearId`` and represent an audit trail for the lifecycle of
|
|
||||||
the fact over time.
|
|
||||||
|
|
||||||
When we want to extend a ``LinearState`` chain (i.e. a sequence of states sharing a ``linearId``), we:
|
|
||||||
|
|
||||||
* Use the ``linearId`` to extract the latest state in the chain from the vault
|
|
||||||
|
|
||||||
* Create a new state that has the same ``linearId``
|
|
||||||
|
|
||||||
* Create a transaction with:
|
|
||||||
|
|
||||||
* The current latest state in the chain as an input
|
|
||||||
|
|
||||||
* The newly-created state as an output
|
|
||||||
|
|
||||||
The new state will now become the latest state in the chain, representing the new current state of the agreement.
|
|
||||||
|
|
||||||
``linearId`` is of type ``UniqueIdentifier``, which is a combination of:
|
|
||||||
|
|
||||||
* A Java ``UUID`` representing a globally unique 128 bit random number
|
|
||||||
* An optional external-reference string for referencing the state in external systems
|
|
||||||
|
|
||||||
OwnableState
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
The ``OwnableState`` interface is defined as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 3
|
|
||||||
:end-before: DOCEND 3
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
* ``owner`` is the ``PublicKey`` of the asset's owner
|
|
||||||
|
|
||||||
* ``withNewOwner(newOwner: AbstractParty)`` creates an copy of the state with a new owner
|
|
||||||
|
|
||||||
Because ``OwnableState`` models fungible assets that can be merged and split over time, ``OwnableState`` instances do
|
|
||||||
not have a ``linearId``. $5 of cash created by one transaction is considered to be identical to $5 of cash produced by
|
|
||||||
another transaction.
|
|
||||||
|
|
||||||
FungibleState
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
``FungibleState<T>`` is an interface to represent things which are fungible, this means that there is an expectation that
|
|
||||||
these things can be split and merged. That's the only assumption made by this interface. This interface should be
|
|
||||||
implemented if you want to represent fractional ownership in a thing, or if you have many things. Examples:
|
|
||||||
|
|
||||||
* There is only one Mona Lisa which you wish to issue 100 tokens, each representing a 1% interest in the Mona Lisa
|
|
||||||
* A company issues 1000 shares with a nominal value of 1, in one batch of 1000. This means the single batch of 1000
|
|
||||||
shares could be split up into 1000 units of 1 share.
|
|
||||||
|
|
||||||
The interface is defined as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/FungibleState.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
As seen, the interface takes a type parameter ``T`` that represents the fungible thing in question. This should describe
|
|
||||||
the basic type of the asset e.g. GBP, USD, oil, shares in company <X>, etc. and any additional metadata (issuer, grade,
|
|
||||||
class, etc.). An upper-bound is not specified for ``T`` to ensure flexibility. Typically, a class would be provided that
|
|
||||||
implements `TokenizableAssetInfo` so the thing can be easily added and subtracted using the ``Amount`` class.
|
|
||||||
|
|
||||||
This interface has been added in addition to ``FungibleAsset`` to provide some additional flexibility which
|
|
||||||
``FungibleAsset`` lacks, in particular:
|
|
||||||
|
|
||||||
* ``FungibleAsset`` defines an amount property of type ``Amount<Issued<T>>``, therefore there is an assumption that all
|
|
||||||
fungible things are issued by a single well known party but this is not always the case.
|
|
||||||
* ``FungibleAsset`` implements ``OwnableState``, as such there is an assumption that all fungible things are ownable.
|
|
||||||
|
|
||||||
Other interfaces
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
You can also customize your state by implementing the following interfaces:
|
|
||||||
|
|
||||||
* ``QueryableState``, which allows the state to be queried in the node's database using custom attributes (see
|
|
||||||
:doc:`api-persistence`)
|
|
||||||
|
|
||||||
* ``SchedulableState``, which allows us to schedule future actions for the state (e.g. a coupon payment on a bond) (see
|
|
||||||
:doc:`event-scheduling`)
|
|
||||||
|
|
||||||
User-defined fields
|
|
||||||
-------------------
|
|
||||||
Beyond implementing ``ContractState`` or a sub-interface, a state is allowed to have any number of additional fields
|
|
||||||
and methods. For example, here is the relatively complex definition for a state representing cash:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../finance/contracts/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
The vault
|
|
||||||
---------
|
|
||||||
Whenever a node records a new transaction, it also decides whether it should store each of the transaction's output
|
|
||||||
states in its vault. The default vault implementation makes the decision based on the following rules:
|
|
||||||
|
|
||||||
* If the state is an ``OwnableState``, the vault will store the state if the node is the state's ``owner``
|
|
||||||
* Otherwise, the vault will store the state if it is one of the ``participants``
|
|
||||||
|
|
||||||
States that are not considered relevant are not stored in the node's vault. However, the node will still store the
|
|
||||||
transactions that created the states in its transaction storage.
|
|
||||||
|
|
||||||
.. _transaction_state:
|
|
||||||
|
|
||||||
TransactionState
|
|
||||||
----------------
|
|
||||||
When a ``ContractState`` is added to a ``TransactionBuilder``, it is wrapped in a ``TransactionState``:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
* ``data`` is the state to be stored on-ledger
|
|
||||||
* ``contract`` is the contract governing evolutions of this state
|
|
||||||
* ``notary`` is the notary service for this state
|
|
||||||
* ``encumbrance`` points to another state that must also appear as an input to any transaction consuming this
|
|
||||||
state
|
|
||||||
* ``constraint`` is a constraint on which contract-code attachments can be used with this state
|
|
||||||
|
|
||||||
.. _reference_states:
|
|
||||||
|
|
||||||
Reference States
|
|
||||||
----------------
|
|
||||||
|
|
||||||
A reference input state is a ``ContractState`` which can be referred to in a transaction by the contracts of input and
|
|
||||||
output states but whose contract is not executed as part of the transaction verification process. Furthermore,
|
|
||||||
reference states are not consumed when the transaction is committed to the ledger but they are checked for
|
|
||||||
"current-ness". In other words, the contract logic isn't run for the referencing transaction only. It's still a normal
|
|
||||||
state when it occurs in an input or output position.
|
|
||||||
|
|
||||||
Reference data states enable many parties to reuse the same state in their transactions as reference data whilst
|
|
||||||
still allowing the reference data state owner the capability to update the state. A standard example would be the
|
|
||||||
creation of financial instrument reference data and the use of such reference data by parties holding the related
|
|
||||||
financial instruments.
|
|
||||||
|
|
||||||
Just like regular input states, the chain of provenance for reference states is resolved and all dependency transactions
|
|
||||||
verified. This is because users of reference data must be satisfied that the data they are referring to is valid as per
|
|
||||||
the rules of the contract which governs it and that all previous participants of the state assented to updates of it.
|
|
||||||
|
|
||||||
**Known limitations:**
|
|
||||||
|
|
||||||
*Notary change:* It is likely the case that users of reference states do not have permission to change the notary
|
|
||||||
assigned to a reference state. Even if users *did* have this permission the result would likely be a bunch of
|
|
||||||
notary change races. As such, if a reference state is added to a transaction which is assigned to a
|
|
||||||
different notary to the input and output states then all those inputs and outputs must be moved to the
|
|
||||||
notary which the reference state uses.
|
|
||||||
|
|
||||||
If two or more reference states assigned to different notaries are added to a transaction then it follows that this
|
|
||||||
transaction cannot be committed to the ledger. This would also be the case for transactions not containing reference
|
|
||||||
states. There is an additional complication for transactions including reference states; it is however, unlikely that the
|
|
||||||
party using the reference states has the authority to change the notary for the state (in other words, the party using the
|
|
||||||
reference state would not be listed as a participant on it). Therefore, it is likely that a transaction containing
|
|
||||||
reference states with two different notaries cannot be committed to the ledger.
|
|
||||||
|
|
||||||
As such, if reference states assigned to multiple different notaries are added to a transaction builder
|
|
||||||
then the check below will fail.
|
|
||||||
|
|
||||||
.. warning:: Currently, encumbrances should not be used with reference states. In the case where a state is
|
|
||||||
encumbered by an encumbrance state, the encumbrance state should also be referenced in the same
|
|
||||||
transaction that references the encumbered state. This is because the data contained within the
|
|
||||||
encumbered state may take on a different meaning, and likely would do, once the encumbrance state
|
|
||||||
is taken into account.
|
|
||||||
|
|
||||||
.. _state_pointers:
|
|
||||||
|
|
||||||
State Pointers
|
|
||||||
--------------
|
|
||||||
|
|
||||||
A ``StatePointer`` contains a pointer to a ``ContractState``. The ``StatePointer`` can be included in a ``ContractState`` as a
|
|
||||||
property, or included in an off-ledger data structure. ``StatePointer`` s can be resolved to a ``StateAndRef`` by performing
|
|
||||||
a look-up. There are two types of pointers; linear and static.
|
|
||||||
|
|
||||||
1. ``StaticPointer`` s are for use with any type of ``ContractState``. The ``StaticPointer`` does as it suggests, it always
|
|
||||||
points to the same ``ContractState``.
|
|
||||||
2. The ``LinearPointer`` is for use with LinearStates. They are particularly useful because due to the way LinearStates
|
|
||||||
work, the pointer will automatically point you to the latest version of a LinearState that the node performing ``resolve``
|
|
||||||
is aware of. In effect, the pointer "moves" as the LinearState is updated.
|
|
||||||
|
|
||||||
State pointers use ``Reference States`` to enable the functionality described above. They can be conceptualized as a mechanism to
|
|
||||||
formalise a development pattern where one needs to refer to a specific state from another transaction (StaticPointer) or a particular lineage
|
|
||||||
of states (LinearPointer). In other words, ``StatePointers`` do not enable a feature in Corda which was previously unavailable.
|
|
||||||
Rather, they help to formalise a pattern which was already possible. In that light, it is worth noting some issues which you may encounter
|
|
||||||
in its application:
|
|
||||||
|
|
||||||
* If the node calling ``resolve`` has not seen any transactions containing a ``ContractState`` which the ``StatePointer``
|
|
||||||
points to, then ``resolve`` will throw an exception. Here, the node calling ``resolve`` might be missing some crucial data.
|
|
||||||
* The node calling ``resolve`` for a ``LinearPointer`` may have seen and stored transactions containing a ``LinearState`` with
|
|
||||||
the specified ``linearId``. However, there is no guarantee the ``StateAndRef<T>`` returned by ``resolve`` is the most recent
|
|
||||||
version of the ``LinearState``. The node only returns the most recent version that _it_ is aware of.
|
|
||||||
|
|
||||||
**Resolving state pointers in TransactionBuilder**
|
|
||||||
|
|
||||||
When building transactions, any ``StatePointer`` s contained within inputs or outputs added to a ``TransactionBuilder`` can
|
|
||||||
be optionally resolved to reference states using the ``resolveStatePointers`` method. The effect is that the pointed to
|
|
||||||
data is carried along with the transaction. This may or may not be appropriate in all circumstances, which is why
|
|
||||||
calling the method is optional.
|
|
@ -1,384 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Testing
|
|
||||||
============
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Flow testing
|
|
||||||
------------
|
|
||||||
|
|
||||||
MockNetwork
|
|
||||||
^^^^^^^^^^^
|
|
||||||
|
|
||||||
Flow testing can be fully automated using a ``MockNetwork`` composed of ``StartedMockNode`` nodes. Each
|
|
||||||
``StartedMockNode`` behaves like a regular Corda node, but its services are either in-memory or mocked out.
|
|
||||||
|
|
||||||
A ``MockNetwork`` is created as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/MockNetworkTestsTutorial.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/MockNetworkTestsTutorial.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
The ``MockNetwork`` requires at a minimum a list of CorDapps to be installed on each ``StartedMockNode``. The CorDapps are looked up on the
|
|
||||||
classpath by package name, using ``TestCordapp.findCordapp``. ``TestCordapp.findCordapp`` scans the current classpath to find the CorDapp that contains the given package.
|
|
||||||
This includes all the associated CorDapp metadata present in its MANIFEST.
|
|
||||||
|
|
||||||
``MockNetworkParameters`` provides other properties for the network which can be tweaked. They default to sensible values if not specified.
|
|
||||||
|
|
||||||
Adding nodes to the network
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Nodes are created on the ``MockNetwork`` using:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/MockNetworkTestsTutorial.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 2
|
|
||||||
:end-before: DOCEND 2
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/MockNetworkTestsTutorial.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 2
|
|
||||||
:end-before: DOCEND 2
|
|
||||||
|
|
||||||
Nodes added using ``createNode`` are provided a default set of node parameters. However, it is also possible to
|
|
||||||
provide different parameters to each node using ``MockNodeParameters``. Of particular interest are ``configOverrides`` which allow you to
|
|
||||||
override some of the default node configuration options. Please refer to the ``MockNodeConfigOverrides`` class for details what can currently
|
|
||||||
be overridden. Also, the ``additionalCordapps`` parameter allows you to add extra CorDapps to a specific node. This is useful when you wish
|
|
||||||
for all nodes to load a common CorDapp but for a subset of nodes to load CorDapps specific to their role in the network.
|
|
||||||
|
|
||||||
Running the network
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
When using a ``MockNetwork``, you must be careful to ensure that all the nodes have processed all the relevant messages
|
|
||||||
before making assertions about the result of performing some action. For example, if you start a flow to update the ledger
|
|
||||||
but don't wait until all the nodes involved have processed all the resulting messages, your nodes' vaults may not be in
|
|
||||||
the state you expect.
|
|
||||||
|
|
||||||
When ``networkSendManuallyPumped`` is set to ``false``, you must manually initiate the processing of received messages.
|
|
||||||
You manually process received messages as follows:
|
|
||||||
|
|
||||||
* ``StartedMockNode.pumpReceive()`` processes a single message from the node's queue
|
|
||||||
* ``MockNetwork.runNetwork()`` processes all the messages in every node's queue until there are no further messages to
|
|
||||||
process
|
|
||||||
|
|
||||||
When ``networkSendManuallyPumped`` is set to ``true``, nodes will automatically process the messages they receive. You
|
|
||||||
can block until all messages have been processed using ``MockNetwork.waitQuiescent()``.
|
|
||||||
|
|
||||||
.. warning:: If ``threadPerNode`` is set to ``true``, ``networkSendManuallyPumped`` must also be set to ``true``.
|
|
||||||
|
|
||||||
Running flows
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
A ``StartedMockNode`` starts a flow using the ``StartedNodeServices.startFlow`` method. This method returns a future
|
|
||||||
representing the output of running the flow.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty))
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
CordaFuture<SignedTransaction> future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty));
|
|
||||||
|
|
||||||
The network must then be manually run before retrieving the future's value:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty))
|
|
||||||
// Assuming network.networkSendManuallyPumped == false.
|
|
||||||
network.runNetwork()
|
|
||||||
val signedTransaction = future.get();
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
CordaFuture<SignedTransaction> future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty));
|
|
||||||
// Assuming network.networkSendManuallyPumped == false.
|
|
||||||
network.runNetwork();
|
|
||||||
SignedTransaction signedTransaction = future.get();
|
|
||||||
|
|
||||||
Accessing ``StartedMockNode`` internals
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Querying a node's vault
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Recorded states can be retrieved from the vault of a ``StartedMockNode`` using:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val myStates = nodeA.services.vaultService.queryBy<MyStateType>().states
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
List<MyStateType> myStates = node.getServices().getVaultService().queryBy(MyStateType.class).getStates();
|
|
||||||
|
|
||||||
This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes.
|
|
||||||
|
|
||||||
|
|
||||||
Examining a node's transaction storage
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Recorded transactions can be retrieved from the transaction storage of a ``StartedMockNode`` using:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val transaction = nodeA.services.validatedTransactions.getTransaction(transaction.id)
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
SignedTransaction transaction = nodeA.getServices().getValidatedTransactions().getTransaction(transaction.getId())
|
|
||||||
|
|
||||||
This allows you to check whether a given transaction has (or has not) been stored, and whether it has the correct
|
|
||||||
attributes.
|
|
||||||
|
|
||||||
This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes.
|
|
||||||
|
|
||||||
Further examples
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* See the flow testing tutorial :doc:`here <flow-testing>`
|
|
||||||
* See the oracle tutorial :doc:`here <oracles>` for information on testing ``@CordaService`` classes
|
|
||||||
* Further examples are available in the Example CorDapp in
|
|
||||||
`Java <|os_samples_branch|/cordapp-example/workflows-java/src/test/java/com/example/test/flow/IOUFlowTests.java>`_ and
|
|
||||||
`Kotlin <|os_samples_branch|/cordapp-example/workflows-kotlin/src/test/kotlin/com/example/test/flow/IOUFlowTests.kt>`_
|
|
||||||
|
|
||||||
Contract testing
|
|
||||||
----------------
|
|
||||||
|
|
||||||
The Corda test framework includes the ability to create a test ledger by calling the ``ledger`` function
|
|
||||||
on an implementation of the ``ServiceHub`` interface.
|
|
||||||
|
|
||||||
Test identities
|
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
You can create dummy identities to use in test transactions using the ``TestIdentity`` class:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 14
|
|
||||||
:end-before: DOCEND 14
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 14
|
|
||||||
:end-before: DOCEND 14
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
``TestIdentity`` exposes the following fields and methods:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val identityParty: Party = bigCorp.party
|
|
||||||
val identityName: CordaX500Name = bigCorp.name
|
|
||||||
val identityPubKey: PublicKey = bigCorp.publicKey
|
|
||||||
val identityKeyPair: KeyPair = bigCorp.keyPair
|
|
||||||
val identityPartyAndCertificate: PartyAndCertificate = bigCorp.identity
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
Party identityParty = bigCorp.getParty();
|
|
||||||
CordaX500Name identityName = bigCorp.getName();
|
|
||||||
PublicKey identityPubKey = bigCorp.getPublicKey();
|
|
||||||
KeyPair identityKeyPair = bigCorp.getKeyPair();
|
|
||||||
PartyAndCertificate identityPartyAndCertificate = bigCorp.getIdentity();
|
|
||||||
|
|
||||||
You can also create a unique ``TestIdentity`` using the ``fresh`` method:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val uniqueTestIdentity: TestIdentity = TestIdentity.fresh("orgName")
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
TestIdentity uniqueTestIdentity = TestIdentity.Companion.fresh("orgName");
|
|
||||||
|
|
||||||
MockServices
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
|
|
||||||
A mock implementation of ``ServiceHub`` is provided in ``MockServices``. This is a minimal ``ServiceHub`` that
|
|
||||||
suffices to test contract logic. It has the ability to insert states into the vault, query the vault, and
|
|
||||||
construct and check transactions.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 11
|
|
||||||
:end-before: DOCEND 11
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 11
|
|
||||||
:end-before: DOCEND 11
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
|
|
||||||
Alternatively, there is a helper constructor which just accepts a list of ``TestIdentity``. The first identity provided is
|
|
||||||
the identity of the node whose ``ServiceHub`` is being mocked, and any subsequent identities are identities that the node
|
|
||||||
knows about. Only the calling package is scanned for cordapps and a test ``IdentityService`` is created
|
|
||||||
for you, using all the given identities.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 12
|
|
||||||
:end-before: DOCEND 12
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 12
|
|
||||||
:end-before: DOCEND 12
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
|
|
||||||
Writing tests using a test ledger
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The ``ServiceHub.ledger`` extension function allows you to create a test ledger. Within the ledger wrapper you can create
|
|
||||||
transactions using the ``transaction`` function. Within a transaction you can define the ``input`` and
|
|
||||||
``output`` states for the transaction, alongside any commands that are being executed, the ``timeWindow`` in which the
|
|
||||||
transaction has been executed, and any ``attachments``, as shown in this example test:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 13
|
|
||||||
:end-before: DOCEND 13
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 13
|
|
||||||
:end-before: DOCEND 13
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
Once all the transaction components have been specified, you can run ``verifies()`` to check that the given transaction is valid.
|
|
||||||
|
|
||||||
Checking for failure states
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
In order to test for failures, you can use the ``failsWith`` method, or in Kotlin the ``fails with`` helper method, which
|
|
||||||
assert that the transaction fails with a specific error. If you just want to assert that the transaction has failed without
|
|
||||||
verifying the message, there is also a ``fails`` method.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 4
|
|
||||||
:end-before: DOCEND 4
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 4
|
|
||||||
:end-before: DOCEND 4
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The transaction DSL forces the last line of the test to be either a ``verifies`` or ``fails with`` statement.
|
|
||||||
|
|
||||||
Testing multiple scenarios at once
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Within a single transaction block, you can assert several times that the transaction constructed so far either passes or
|
|
||||||
fails verification. For example, you could test that a contract fails to verify because it has no output states, and then
|
|
||||||
add the relevant output state and check that the contract verifies successfully, as in the following example:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 5
|
|
||||||
:end-before: DOCEND 5
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 5
|
|
||||||
:end-before: DOCEND 5
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
You can also use the ``tweak`` function to create a locally scoped transaction that you can make changes to
|
|
||||||
and then return to the original, unmodified transaction. As in the following example:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 7
|
|
||||||
:end-before: DOCEND 7
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 7
|
|
||||||
:end-before: DOCEND 7
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
|
|
||||||
Chaining transactions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The following example shows that within a ``ledger``, you can create more than one ``transaction`` in order to test chains
|
|
||||||
of transactions. In addition to ``transaction``, ``unverifiedTransaction`` can be used, as in the example below, to create
|
|
||||||
transactions on the ledger without verifying them, for pre-populating the ledger with existing data. When chaining transactions,
|
|
||||||
it is important to note that even though a ``transaction`` ``verifies`` successfully, the overall ledger may not be valid. This can
|
|
||||||
be verified separately by placing a ``verifies`` or ``fails`` statement within the ``ledger`` block.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/kotlin/tutorial/testdsl/TutorialTestDSL.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 9
|
|
||||||
:end-before: DOCEND 9
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/TutorialTestDSL.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 9
|
|
||||||
:end-before: DOCEND 9
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
|
|
||||||
Further examples
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* See the flow testing tutorial :doc:`here <tutorial-test-dsl>`
|
|
||||||
* Further examples are available in the Example CorDapp in
|
|
||||||
`Java <|os_samples_branch|/cordapp-example/workflows-java/src/test/java/com/example/test/flow/IOUFlowTests.java>`_ and
|
|
||||||
`Kotlin <|os_samples_branch|/cordapp-example/workflows-kotlin/src/test/kotlin/com/example/test/flow/IOUFlowTests.kt>`_
|
|
@ -1,783 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Transactions
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-transactions`.
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Transaction lifecycle
|
|
||||||
---------------------
|
|
||||||
Between its creation and its final inclusion on the ledger, a transaction will generally occupy one of three states:
|
|
||||||
|
|
||||||
* ``TransactionBuilder``. A transaction's initial state. This is the only state during which the transaction is
|
|
||||||
mutable, so we must add all the required components before moving on.
|
|
||||||
|
|
||||||
* ``SignedTransaction``. The transaction now has one or more digital signatures, making it immutable. This is the
|
|
||||||
transaction type that is passed around to collect additional signatures and that is recorded on the ledger.
|
|
||||||
|
|
||||||
* ``LedgerTransaction``. The transaction has been "resolved" - for example, its inputs have been converted from
|
|
||||||
references to actual states - allowing the transaction to be fully inspected.
|
|
||||||
|
|
||||||
We can visualise the transitions between the three stages as follows:
|
|
||||||
|
|
||||||
.. image:: resources/transaction-flow.png
|
|
||||||
|
|
||||||
Transaction components
|
|
||||||
----------------------
|
|
||||||
A transaction consists of six types of components:
|
|
||||||
|
|
||||||
* 1+ states:
|
|
||||||
|
|
||||||
* 0+ input states
|
|
||||||
* 0+ output states
|
|
||||||
* 0+ reference input states
|
|
||||||
|
|
||||||
* 1+ commands
|
|
||||||
* 0+ attachments
|
|
||||||
* 0 or 1 time-window
|
|
||||||
|
|
||||||
* A transaction with a time-window must also have a notary
|
|
||||||
|
|
||||||
Each component corresponds to a specific class in the Corda API. The following section describes each component class,
|
|
||||||
and how it is created.
|
|
||||||
|
|
||||||
Input states
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
An input state is added to a transaction as a ``StateAndRef``, which combines:
|
|
||||||
|
|
||||||
* The ``ContractState`` itself
|
|
||||||
* A ``StateRef`` identifying this ``ContractState`` as the output of a specific transaction
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 21
|
|
||||||
:end-before: DOCEND 21
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 21
|
|
||||||
:end-before: DOCEND 21
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
A ``StateRef`` uniquely identifies an input state, allowing the notary to mark it as historic. It is made up of:
|
|
||||||
|
|
||||||
* The hash of the transaction that generated the state
|
|
||||||
* The state's index in the outputs of that transaction
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 20
|
|
||||||
:end-before: DOCEND 20
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 20
|
|
||||||
:end-before: DOCEND 20
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
The ``StateRef`` links an input state back to the transaction that created it. This means that transactions form
|
|
||||||
"chains" linking each input back to an original issuance transaction. This allows nodes verifying the transaction
|
|
||||||
to "walk the chain" and verify that each input was generated through a valid sequence of transactions.
|
|
||||||
|
|
||||||
Reference input states
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. warning:: Reference states are only available on Corda networks with a minimum platform version >= 4.
|
|
||||||
|
|
||||||
A reference input state is added to a transaction as a ``ReferencedStateAndRef``. A ``ReferencedStateAndRef`` can be
|
|
||||||
obtained from a ``StateAndRef`` by calling the ``StateAndRef.referenced()`` method which returns a ``ReferencedStateAndRef``.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 55
|
|
||||||
:end-before: DOCEND 55
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 55
|
|
||||||
:end-before: DOCEND 55
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
**Handling of update races:**
|
|
||||||
|
|
||||||
When using reference states in a transaction, it may be the case that a notarisation failure occurs. This is most likely
|
|
||||||
because the creator of the state (being used as a reference state in your transaction), has just updated it.
|
|
||||||
|
|
||||||
Typically, the creator of such reference data will have implemented flows for syndicating the updates out to users.
|
|
||||||
However it is inevitable that there will be a delay between the state being used as a reference being consumed, and the
|
|
||||||
nodes using it receiving the update.
|
|
||||||
|
|
||||||
This is where the ``WithReferencedStatesFlow`` comes in. Given a flow which uses reference states, the
|
|
||||||
``WithReferencedStatesFlow`` will execute the the flow as a subFlow. If the flow fails due to a ``NotaryError.Conflict``
|
|
||||||
for a reference state, then it will be suspended until the state refs for the reference states are consumed. In this
|
|
||||||
case, a consumption means that:
|
|
||||||
|
|
||||||
1. the owner of the reference state has updated the state with a valid, notarised transaction
|
|
||||||
2. the owner of the reference state has shared the update with the node attempting to run the flow which uses the
|
|
||||||
reference state
|
|
||||||
3. The node has successfully committed the transaction updating the reference state (and all the dependencies), and
|
|
||||||
added the updated reference state to the vault.
|
|
||||||
|
|
||||||
At the point where the transaction updating the state being used as a reference is committed to storage and the vault
|
|
||||||
update occurs, then the ``WithReferencedStatesFlow`` will wake up and re-execute the provided flow.
|
|
||||||
|
|
||||||
.. warning:: Caution should be taken when using this flow as it facilitates automated re-running of flows which use
|
|
||||||
reference states. The flow using reference states should include checks to ensure that the reference data is
|
|
||||||
reasonable, especially if the economics of the transaction depends upon the data contained within a reference state.
|
|
||||||
|
|
||||||
Output states
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
Since a transaction's output states do not exist until the transaction is committed, they cannot be referenced as the
|
|
||||||
outputs of previous transactions. Instead, we create the desired output states as ``ContractState`` instances, and
|
|
||||||
add them to the transaction directly:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 22
|
|
||||||
:end-before: DOCEND 22
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 22
|
|
||||||
:end-before: DOCEND 22
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
In cases where an output state represents an update of an input state, we may want to create the output state by basing
|
|
||||||
it on the input state:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 23
|
|
||||||
:end-before: DOCEND 23
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 23
|
|
||||||
:end-before: DOCEND 23
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Before our output state can be added to a transaction, we need to associate it with a contract. We can do this by
|
|
||||||
wrapping the output state in a ``StateAndContract``, which combines:
|
|
||||||
|
|
||||||
* The ``ContractState`` representing the output states
|
|
||||||
* A ``String`` identifying the contract governing the state
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 47
|
|
||||||
:end-before: DOCEND 47
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 47
|
|
||||||
:end-before: DOCEND 47
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Commands
|
|
||||||
^^^^^^^^
|
|
||||||
A command is added to the transaction as a ``Command``, which combines:
|
|
||||||
|
|
||||||
* A ``CommandData`` instance indicating the command's type
|
|
||||||
* A ``List<PublicKey>`` representing the command's required signers
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 24
|
|
||||||
:end-before: DOCEND 24
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 24
|
|
||||||
:end-before: DOCEND 24
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Attachments
|
|
||||||
^^^^^^^^^^^
|
|
||||||
Attachments are identified by their hash:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 25
|
|
||||||
:end-before: DOCEND 25
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 25
|
|
||||||
:end-before: DOCEND 25
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
The attachment with the corresponding hash must have been uploaded ahead of time via the node's RPC interface.
|
|
||||||
|
|
||||||
Time-windows
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
Time windows represent the period during which the transaction must be notarised. They can have a start and an end
|
|
||||||
time, or be open at either end:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 26
|
|
||||||
:end-before: DOCEND 26
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 26
|
|
||||||
:end-before: DOCEND 26
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
We can also define a time window as an ``Instant`` plus/minus a time tolerance (e.g. 30 seconds):
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 42
|
|
||||||
:end-before: DOCEND 42
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 42
|
|
||||||
:end-before: DOCEND 42
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Or as a start-time plus a duration:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 43
|
|
||||||
:end-before: DOCEND 43
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 43
|
|
||||||
:end-before: DOCEND 43
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
TransactionBuilder
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Creating a builder
|
|
||||||
^^^^^^^^^^^^^^^^^^
|
|
||||||
The first step when creating a transaction proposal is to instantiate a ``TransactionBuilder``.
|
|
||||||
|
|
||||||
If the transaction has input states or a time-window, we need to instantiate the builder with a reference to the notary
|
|
||||||
that will notarise the inputs and verify the time-window:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 19
|
|
||||||
:end-before: DOCEND 19
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 19
|
|
||||||
:end-before: DOCEND 19
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
We discuss the selection of a notary in :doc:`api-flows`.
|
|
||||||
|
|
||||||
If the transaction does not have any input states or a time-window, it does not require a notary, and can be
|
|
||||||
instantiated without one:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 46
|
|
||||||
:end-before: DOCEND 46
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 46
|
|
||||||
:end-before: DOCEND 46
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Adding items
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
The next step is to build up the transaction proposal by adding the desired components.
|
|
||||||
|
|
||||||
We can add components to the builder using the ``TransactionBuilder.withItems`` method:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
``withItems`` takes a ``vararg`` of objects and adds them to the builder based on their type:
|
|
||||||
|
|
||||||
* ``StateAndRef`` objects are added as input states
|
|
||||||
* ``ReferencedStateAndRef`` objects are added as reference input states
|
|
||||||
* ``TransactionState`` and ``StateAndContract`` objects are added as output states
|
|
||||||
|
|
||||||
* Both ``TransactionState`` and ``StateAndContract`` are wrappers around a ``ContractState`` output that link the
|
|
||||||
output to a specific contract
|
|
||||||
|
|
||||||
* ``Command`` objects are added as commands
|
|
||||||
* ``SecureHash`` objects are added as attachments
|
|
||||||
* A ``TimeWindow`` object replaces the transaction's existing ``TimeWindow``, if any
|
|
||||||
|
|
||||||
Passing in objects of any other type will cause an ``IllegalArgumentException`` to be thrown.
|
|
||||||
|
|
||||||
Here's an example usage of ``TransactionBuilder.withItems``:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 27
|
|
||||||
:end-before: DOCEND 27
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 27
|
|
||||||
:end-before: DOCEND 27
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
There are also individual methods for adding components.
|
|
||||||
|
|
||||||
Here are the methods for adding inputs and attachments:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 28
|
|
||||||
:end-before: DOCEND 28
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 28
|
|
||||||
:end-before: DOCEND 28
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
An output state can be added as a ``ContractState``, contract class name and notary:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 49
|
|
||||||
:end-before: DOCEND 49
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 49
|
|
||||||
:end-before: DOCEND 49
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
We can also leave the notary field blank, in which case the transaction's default notary is used:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 50
|
|
||||||
:end-before: DOCEND 50
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 50
|
|
||||||
:end-before: DOCEND 50
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Or we can add the output state as a ``TransactionState``, which already specifies the output's contract and notary:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 51
|
|
||||||
:end-before: DOCEND 51
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 51
|
|
||||||
:end-before: DOCEND 51
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Commands can be added as a ``Command``:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 52
|
|
||||||
:end-before: DOCEND 52
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 52
|
|
||||||
:end-before: DOCEND 52
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Or as ``CommandData`` and a ``vararg PublicKey``:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 53
|
|
||||||
:end-before: DOCEND 53
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 53
|
|
||||||
:end-before: DOCEND 53
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
For the time-window, we can set a time-window directly:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 44
|
|
||||||
:end-before: DOCEND 44
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 44
|
|
||||||
:end-before: DOCEND 44
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Or define the time-window as a time plus a duration (e.g. 45 seconds):
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 45
|
|
||||||
:end-before: DOCEND 45
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 45
|
|
||||||
:end-before: DOCEND 45
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Signing the builder
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
Once the builder is ready, we finalize it by signing it and converting it into a ``SignedTransaction``.
|
|
||||||
|
|
||||||
We can either sign with our legal identity key:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 29
|
|
||||||
:end-before: DOCEND 29
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 29
|
|
||||||
:end-before: DOCEND 29
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Or we can also choose to use another one of our public keys:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 30
|
|
||||||
:end-before: DOCEND 30
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 30
|
|
||||||
:end-before: DOCEND 30
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Either way, the outcome of this process is to create an immutable ``SignedTransaction`` with our signature over it.
|
|
||||||
|
|
||||||
SignedTransaction
|
|
||||||
-----------------
|
|
||||||
A ``SignedTransaction`` is a combination of:
|
|
||||||
|
|
||||||
* An immutable transaction
|
|
||||||
* A list of signatures over that transaction
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 1
|
|
||||||
:end-before: DOCEND 1
|
|
||||||
|
|
||||||
Before adding our signature to the transaction, we'll want to verify both the transaction's contents and the
|
|
||||||
transaction's signatures.
|
|
||||||
|
|
||||||
Verifying the transaction's contents
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
If a transaction has inputs, we need to retrieve all the states in the transaction's dependency chain before we can
|
|
||||||
verify the transaction's contents. This is because the transaction is only valid if its dependency chain is also valid.
|
|
||||||
We do this by requesting any states in the chain that our node doesn't currently have in its local storage from the
|
|
||||||
proposer(s) of the transaction. This process is handled by a built-in flow called ``ReceiveTransactionFlow``.
|
|
||||||
See :doc:`api-flows` for more details.
|
|
||||||
|
|
||||||
We can now verify the transaction's contents to ensure that it satisfies the contracts of all the transaction's input
|
|
||||||
and output states:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 33
|
|
||||||
:end-before: DOCEND 33
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 33
|
|
||||||
:end-before: DOCEND 33
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
Checking that the transaction meets the contract constraints is only part of verifying the transaction's contents. We
|
|
||||||
will usually also want to perform our own additional validation of the transaction contents before signing, to ensure
|
|
||||||
that the transaction proposal represents an agreement we wish to enter into.
|
|
||||||
|
|
||||||
However, the ``SignedTransaction`` holds its inputs as ``StateRef`` instances, and its attachments as ``SecureHash``
|
|
||||||
instances, which do not provide enough information to properly validate the transaction's contents. We first need to
|
|
||||||
resolve the ``StateRef`` and ``SecureHash`` instances into actual ``ContractState`` and ``Attachment`` instances, which
|
|
||||||
we can then inspect.
|
|
||||||
|
|
||||||
We achieve this by using the ``ServiceHub`` to convert the ``SignedTransaction`` into a ``LedgerTransaction``:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 32
|
|
||||||
:end-before: DOCEND 32
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 32
|
|
||||||
:end-before: DOCEND 32
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
We can now perform our additional verification. Here's a simple example:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 34
|
|
||||||
:end-before: DOCEND 34
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 34
|
|
||||||
:end-before: DOCEND 34
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
Verifying the transaction's signatures
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
Aside from verifying that the transaction's contents are valid, we also need to check that the signatures are valid. A
|
|
||||||
valid signature over the hash of the transaction prevents tampering.
|
|
||||||
|
|
||||||
We can verify that all the transaction's required signatures are present and valid as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 35
|
|
||||||
:end-before: DOCEND 35
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 35
|
|
||||||
:end-before: DOCEND 35
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
However, we'll often want to verify the transaction's existing signatures before all of them have been collected. For
|
|
||||||
this we can use ``SignedTransaction.verifySignaturesExcept``, which takes a ``vararg`` of the public keys for
|
|
||||||
which the signatures are allowed to be missing:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 36
|
|
||||||
:end-before: DOCEND 36
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 36
|
|
||||||
:end-before: DOCEND 36
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
There is also an overload of ``SignedTransaction.verifySignaturesExcept``, which takes a ``Collection`` of the
|
|
||||||
public keys for which the signatures are allowed to be missing:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 54
|
|
||||||
:end-before: DOCEND 54
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 54
|
|
||||||
:end-before: DOCEND 54
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
|
|
||||||
If the transaction is missing any signatures without the corresponding public keys being passed in, a
|
|
||||||
``SignaturesMissingException`` is thrown.
|
|
||||||
|
|
||||||
We can also choose to simply verify the signatures that are present:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 37
|
|
||||||
:end-before: DOCEND 37
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 37
|
|
||||||
:end-before: DOCEND 37
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
Be very careful, however - this function neither guarantees that the signatures that are present are required, nor
|
|
||||||
checks whether any signatures are missing.
|
|
||||||
|
|
||||||
Signing the transaction
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
Once we are satisfied with the contents and existing signatures over the transaction, we add our signature to the
|
|
||||||
``SignedTransaction`` to indicate that we approve the transaction.
|
|
||||||
|
|
||||||
We can sign using our legal identity key, as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 38
|
|
||||||
:end-before: DOCEND 38
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 38
|
|
||||||
:end-before: DOCEND 38
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Or we can choose to sign using another one of our public keys:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 39
|
|
||||||
:end-before: DOCEND 39
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 39
|
|
||||||
:end-before: DOCEND 39
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
We can also generate a signature over the transaction without adding it to the transaction directly.
|
|
||||||
|
|
||||||
We can do this with our legal identity key:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 40
|
|
||||||
:end-before: DOCEND 40
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 40
|
|
||||||
:end-before: DOCEND 40
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Or using another one of our public keys:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART 41
|
|
||||||
:end-before: DOCEND 41
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART 41
|
|
||||||
:end-before: DOCEND 41
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Notarising and recording
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See :doc:`api-flows` for
|
|
||||||
more details.
|
|
@ -1,675 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
API: Vault Query
|
|
||||||
================
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Overview
|
|
||||||
--------
|
|
||||||
Corda has been architected from the ground up to encourage usage of industry standard, proven query frameworks and
|
|
||||||
libraries for accessing RDBMS backed transactional stores (including the Vault).
|
|
||||||
|
|
||||||
Corda provides a number of flexible query mechanisms for accessing the Vault:
|
|
||||||
|
|
||||||
- Vault Query API
|
|
||||||
- Using a JDBC session (as described in :ref:`Persistence <jdbc_session_ref>`)
|
|
||||||
- Custom JPA_/JPQL_ queries
|
|
||||||
- Custom 3rd party Data Access frameworks such as `Spring Data <http://projects.spring.io/spring-data>`_
|
|
||||||
|
|
||||||
The majority of query requirements can be satisfied by using the Vault Query API, which is exposed via the
|
|
||||||
``VaultService`` for use directly by flows:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/VaultService.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryAPI
|
|
||||||
:end-before: DOCEND VaultQueryAPI
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
And via ``CordaRPCOps`` for use by RPC client applications:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryByAPI
|
|
||||||
:end-before: DOCEND VaultQueryByAPI
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultTrackByAPI
|
|
||||||
:end-before: DOCEND VaultTrackByAPI
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
Helper methods are also provided with default values for arguments:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryAPIHelpers
|
|
||||||
:end-before: DOCEND VaultQueryAPIHelpers
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultTrackAPIHelpers
|
|
||||||
:end-before: DOCEND VaultTrackAPIHelpers
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of
|
|
||||||
filter criteria:
|
|
||||||
|
|
||||||
- Use ``queryBy`` to obtain a current snapshot of data (for a given ``QueryCriteria``)
|
|
||||||
- Use ``trackBy`` to obtain both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
|
|
||||||
|
|
||||||
.. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL).
|
|
||||||
They will not respect any other criteria that the initial query has been filtered by.
|
|
||||||
|
|
||||||
Simple pagination (page number and size) and sorting (directional ordering using standard or custom property
|
|
||||||
attributes) is also specifiable. Defaults are defined for paging (pageNumber = 1, pageSize = 200) and sorting
|
|
||||||
(direction = ASC).
|
|
||||||
|
|
||||||
The ``QueryCriteria`` interface provides a flexible mechanism for specifying different filtering criteria, including
|
|
||||||
and/or composition and a rich set of operators to include:
|
|
||||||
|
|
||||||
* Binary logical (AND, OR)
|
|
||||||
* Comparison (LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL)
|
|
||||||
* Equality (EQUAL, NOT_EQUAL)
|
|
||||||
* Likeness (LIKE, NOT_LIKE)
|
|
||||||
* Nullability (IS_NULL, NOT_NULL)
|
|
||||||
* Collection based (IN, NOT_IN)
|
|
||||||
* Standard SQL-92 aggregate functions (SUM, AVG, MIN, MAX, COUNT)
|
|
||||||
|
|
||||||
There are four implementations of this interface which can be chained together to define advanced filters.
|
|
||||||
|
|
||||||
1. ``VaultQueryCriteria`` provides filterable criteria on attributes within the Vault states table: status (UNCONSUMED,
|
|
||||||
CONSUMED), state reference(s), contract state type(s), notaries, soft locked states, timestamps (RECORDED, CONSUMED),
|
|
||||||
state constraints (see :ref:`Constraint Types <implicit_constraint_types>`), relevancy (ALL, RELEVANT, NON_RELEVANT),
|
|
||||||
participants (exact or any match).
|
|
||||||
|
|
||||||
.. note:: Sensible defaults are defined for frequently used attributes (status = UNCONSUMED, always include soft
|
|
||||||
locked states).
|
|
||||||
|
|
||||||
2. ``FungibleAssetQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core
|
|
||||||
``FungibleAsset`` contract state interface, used to represent assets that are fungible, countable and issued by a
|
|
||||||
specific party (eg. ``Cash.State`` and ``CommodityContract.State`` in the Corda finance module). Filterable
|
|
||||||
attributes include: participants (exact or any match), owner(s), quantity, issuer party(s) and issuer reference(s).
|
|
||||||
|
|
||||||
.. note:: All contract states that extend the ``FungibleAsset`` now automatically persist that interfaces common
|
|
||||||
state attributes to the **vault_fungible_states** table.
|
|
||||||
|
|
||||||
3. ``LinearStateQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``LinearState``
|
|
||||||
and ``DealState`` contract state interfaces, used to represent entities that continuously supersede themselves, all
|
|
||||||
of which share the same ``linearId`` (e.g. trade entity states such as the ``IRSState`` defined in the SIMM
|
|
||||||
valuation demo). Filterable attributes include: participants (exact or any match), linearId(s), uuid(s), and externalId(s).
|
|
||||||
|
|
||||||
.. note:: All contract states that extend ``LinearState`` or ``DealState`` now automatically persist those
|
|
||||||
interfaces common state attributes to the **vault_linear_states** table.
|
|
||||||
|
|
||||||
4. ``VaultCustomQueryCriteria`` provides the means to specify one or many arbitrary expressions on attributes defined
|
|
||||||
by a custom contract state that implements its own schema as described in the :doc:`Persistence </api-persistence>`
|
|
||||||
documentation and associated examples. Custom criteria expressions are expressed using one of several type-safe
|
|
||||||
``CriteriaExpression``: BinaryLogical, Not, ColumnPredicateExpression, AggregateFunctionExpression. The
|
|
||||||
``ColumnPredicateExpression`` allows for specification arbitrary criteria using the previously enumerated operator
|
|
||||||
types. The ``AggregateFunctionExpression`` allows for the specification of an aggregate function type (sum, avg,
|
|
||||||
max, min, count) with optional grouping and sorting. Furthermore, a rich DSL is provided to enable simple
|
|
||||||
construction of custom criteria using any combination of ``ColumnPredicate``. See the ``Builder`` object in
|
|
||||||
``QueryCriteriaUtils`` for a complete specification of the DSL.
|
|
||||||
|
|
||||||
.. note:: Custom contract schemas are automatically registered upon node startup for CorDapps. Please refer to
|
|
||||||
:doc:`Persistence </api-persistence>` for mechanisms of registering custom schemas for different testing
|
|
||||||
purposes.
|
|
||||||
|
|
||||||
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators.
|
|
||||||
|
|
||||||
All ``QueryCriteria`` implementations provide an explicitly specifiable set of common attributes:
|
|
||||||
|
|
||||||
1. State status attribute (``Vault.StateStatus``), which defaults to filtering on UNCONSUMED states.
|
|
||||||
When chaining several criteria using AND / OR, the last value of this attribute will override any previous
|
|
||||||
2. Contract state types (``<Set<Class<out ContractState>>``), which will contain at minimum one type (by default this
|
|
||||||
will be ``ContractState`` which resolves to all state types). When chaining several criteria using ``and`` and
|
|
||||||
``or`` operators, all specified contract state types are combined into a single set
|
|
||||||
|
|
||||||
An example of a custom query is illustrated here:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample20
|
|
||||||
:end-before: DOCEND VaultQueryExample20
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: Custom contract states that implement the ``Queryable`` interface may now extend common schemas types
|
|
||||||
``FungiblePersistentState`` or, ``LinearPersistentState``. Previously, all custom contracts extended the root
|
|
||||||
``PersistentState`` class and defined repeated mappings of ``FungibleAsset`` and ``LinearState`` attributes. See
|
|
||||||
``SampleCashSchemaV2`` and ``DummyLinearStateSchemaV2`` as examples.
|
|
||||||
|
|
||||||
Examples of these ``QueryCriteria`` objects are presented below for Kotlin and Java.
|
|
||||||
|
|
||||||
.. note:: When specifying the ``ContractType`` as a parameterised type to the ``QueryCriteria`` in Kotlin, queries now
|
|
||||||
include all concrete implementations of that type if this is an interface. Previously, it was only possible to query
|
|
||||||
on concrete types (or the universe of all ``ContractState``).
|
|
||||||
|
|
||||||
The Vault Query API leverages the rich semantics of the underlying JPA Hibernate_ based
|
|
||||||
:doc:`Persistence </api-persistence>` framework adopted by Corda.
|
|
||||||
|
|
||||||
.. _Hibernate: https://docs.jboss.org/hibernate/jpa/2.1/api/
|
|
||||||
|
|
||||||
.. note:: Permissioning at the database level will be enforced at a later date to ensure authenticated, role-based,
|
|
||||||
read-only access to underlying Corda tables.
|
|
||||||
|
|
||||||
.. note:: API's now provide ease of use calling semantics from both Java and Kotlin. However, it should be noted that
|
|
||||||
Java custom queries are significantly more verbose due to the use of reflection fields to reference schema attribute
|
|
||||||
types.
|
|
||||||
|
|
||||||
An example of a custom query in Java is illustrated here:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample3
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample3
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
.. note:: Queries by ``Party`` specify the ``AbstractParty`` which may be concrete or anonymous. In the later case,
|
|
||||||
where an anonymous party does not resolve to an X500 name via the ``IdentityService``, no query results will ever be
|
|
||||||
produced. For performance reasons, queries do not use ``PublicKey`` as search criteria.
|
|
||||||
|
|
||||||
Custom queries can be either case sensitive or case insensitive. They are defined via a ``Boolean`` as one of the function parameters of each operator function. By default each operator is case sensitive.
|
|
||||||
|
|
||||||
An example of a case sensitive custom query operator is illustrated here:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, true)
|
|
||||||
|
|
||||||
.. note:: The ``Boolean`` input of ``true`` in this example could be removed since the function will default to ``true`` when not provided.
|
|
||||||
|
|
||||||
An example of a case insensitive custom query operator is illustrated here:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, false)
|
|
||||||
|
|
||||||
An example of a case sensitive custom query operator in Java is illustrated here:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class);
|
|
||||||
CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD", true);
|
|
||||||
|
|
||||||
An example of a case insensitive custom query operator in Java is illustrated here:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class);
|
|
||||||
CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD", false);
|
|
||||||
|
|
||||||
Pagination
|
|
||||||
----------
|
|
||||||
The API provides support for paging where large numbers of results are expected (by default, a page size is set to 200
|
|
||||||
results). Defining a sensible default page size enables efficient checkpointing within flows, and frees the developer
|
|
||||||
from worrying about pagination where result sets are expected to be constrained to 200 or fewer entries. Where large
|
|
||||||
result sets are expected (such as using the RPC API for reporting and/or UI display), it is strongly recommended to
|
|
||||||
define a ``PageSpecification`` to correctly process results with efficient memory utilisation. A fail-fast mode is in
|
|
||||||
place to alert API users to the need for pagination where a single query returns more than 200 results and no
|
|
||||||
``PageSpecification`` has been supplied.
|
|
||||||
|
|
||||||
Here's a query that extracts every unconsumed ``ContractState`` from the vault in pages of size 200, starting from the
|
|
||||||
default page number (page one):
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val vaultSnapshot = proxy.vaultQueryBy<ContractState>(
|
|
||||||
QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED),
|
|
||||||
PageSpecification(DEFAULT_PAGE_NUM, 200))
|
|
||||||
|
|
||||||
.. note:: A pages maximum size ``MAX_PAGE_SIZE`` is defined as ``Int.MAX_VALUE`` and should be used with extreme
|
|
||||||
caution as results returned may exceed your JVM's memory footprint.
|
|
||||||
|
|
||||||
Example usage
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Kotlin
|
|
||||||
^^^^^^
|
|
||||||
|
|
||||||
**General snapshot queries using** ``VaultQueryCriteria``:
|
|
||||||
|
|
||||||
Query for all unconsumed states (simplest query possible):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample1
|
|
||||||
:end-before: DOCEND VaultQueryExample1
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states for some state references:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample2
|
|
||||||
:end-before: DOCEND VaultQueryExample2
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states for several contract state types:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample3
|
|
||||||
:end-before: DOCEND VaultQueryExample3
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states for specified contract state constraint types and sorted in ascending alphabetical order:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample30
|
|
||||||
:end-before: DOCEND VaultQueryExample30
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states for specified contract state constraints (type and data):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample31
|
|
||||||
:end-before: DOCEND VaultQueryExample31
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states for a given notary:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample4
|
|
||||||
:end-before: DOCEND VaultQueryExample4
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states for a given set of participants (matches any state that contains at least one of the specified participants):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample5
|
|
||||||
:end-before: DOCEND VaultQueryExample5
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states for a given set of participants (exactly matches only states that contain all specified participants):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample51
|
|
||||||
:end-before: DOCEND VaultQueryExample51
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed states recorded between two time intervals:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample6
|
|
||||||
:end-before: DOCEND VaultQueryExample6
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: This example illustrates usage of a ``Between`` ``ColumnPredicate``.
|
|
||||||
|
|
||||||
Query for all states with pagination specification (10 results per page):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample7
|
|
||||||
:end-before: DOCEND VaultQueryExample7
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: The result set metadata field `totalStatesAvailable` allows you to further paginate accordingly as
|
|
||||||
demonstrated in the following example.
|
|
||||||
|
|
||||||
Query for all states using a pagination specification and iterate using the `totalStatesAvailable` field until no further
|
|
||||||
pages available:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample24
|
|
||||||
:end-before: DOCEND VaultQueryExample24
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
Query for only relevant states in the vault:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample25
|
|
||||||
:end-before: DOCEND VaultQueryExample25
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
**LinearState and DealState queries using** ``LinearStateQueryCriteria``:
|
|
||||||
|
|
||||||
Query for unconsumed linear states for given linear ids:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample8
|
|
||||||
:end-before: DOCEND VaultQueryExample8
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for all linear states associated with a linear id:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample9
|
|
||||||
:end-before: DOCEND VaultQueryExample9
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed deal states with deals references:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample10
|
|
||||||
:end-before: DOCEND VaultQueryExample10
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed deal states with deals parties (any match):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample11
|
|
||||||
:end-before: DOCEND VaultQueryExample11
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for unconsumed deal states with deals parties (exact match):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample52
|
|
||||||
:end-before: DOCEND VaultQueryExample52
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for only relevant linear states in the vault:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample26
|
|
||||||
:end-before: DOCEND VaultQueryExample26
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
**FungibleAsset and DealState queries using** ``FungibleAssetQueryCriteria``:
|
|
||||||
|
|
||||||
Query for fungible assets for a given currency:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample12
|
|
||||||
:end-before: DOCEND VaultQueryExample12
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for fungible assets for a minimum quantity:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample13
|
|
||||||
:end-before: DOCEND VaultQueryExample13
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: This example uses the builder DSL.
|
|
||||||
|
|
||||||
Query for fungible assets for a specific issuer party:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample14
|
|
||||||
:end-before: DOCEND VaultQueryExample14
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for only relevant fungible states in the vault:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample27
|
|
||||||
:end-before: DOCEND VaultQueryExample27
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
**Aggregate Function queries using** ``VaultCustomQueryCriteria``:
|
|
||||||
|
|
||||||
.. note:: Query results for aggregate functions are contained in the ``otherResults`` attribute of a results Page.
|
|
||||||
|
|
||||||
Aggregations on cash using various functions:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample21
|
|
||||||
:end-before: DOCEND VaultQueryExample21
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: ``otherResults`` will contain 5 items, one per calculated aggregate function.
|
|
||||||
|
|
||||||
Aggregations on cash grouped by currency for various functions:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample22
|
|
||||||
:end-before: DOCEND VaultQueryExample22
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: ``otherResults`` will contain 24 items, one result per calculated aggregate function per currency (the
|
|
||||||
grouping attribute - currency in this case - is returned per aggregate result).
|
|
||||||
|
|
||||||
Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample23
|
|
||||||
:end-before: DOCEND VaultQueryExample23
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: ``otherResults`` will contain 12 items sorted from largest summed cash amount to smallest, one result per
|
|
||||||
calculated aggregate function per issuer party and currency (grouping attributes are returned per aggregate result).
|
|
||||||
|
|
||||||
Dynamic queries (also using ``VaultQueryCriteria``) are an extension to the snapshot queries by returning an
|
|
||||||
additional ``QueryResults`` return type in the form of an ``Observable<Vault.Update>``. Refer to
|
|
||||||
`ReactiveX Observable <http://reactivex.io/documentation/observable.html>`_ for a detailed understanding and usage of
|
|
||||||
this type.
|
|
||||||
|
|
||||||
Track unconsumed cash states:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample15
|
|
||||||
:end-before: DOCEND VaultQueryExample15
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Track unconsumed linear states:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample16
|
|
||||||
:end-before: DOCEND VaultQueryExample16
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: This will return both ``DealState`` and ``LinearState`` states.
|
|
||||||
|
|
||||||
Track unconsumed deal states:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART VaultQueryExample17
|
|
||||||
:end-before: DOCEND VaultQueryExample17
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
.. note:: This will return only ``DealState`` states.
|
|
||||||
|
|
||||||
Java examples
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Query for all unconsumed linear states:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample0
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample0
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for all consumed cash states:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample1
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample1
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for consumed deal states or linear ids, specify a paging specification and sort by unique identifier:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample2
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample2
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Query for all states using a pagination specification and iterate using the `totalStatesAvailable` field until no further pages available:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultQueryExample24
|
|
||||||
:end-before: DOCEND VaultQueryExample24
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
**Aggregate Function queries using** ``VaultCustomQueryCriteria``:
|
|
||||||
|
|
||||||
Aggregations on cash using various functions:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample21
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample21
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
Aggregations on cash grouped by currency for various functions:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample22
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample22
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample23
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample23
|
|
||||||
:dedent: 16
|
|
||||||
|
|
||||||
Track unconsumed cash states:
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample4
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample4
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Track unconsumed deal states or linear states (with snapshot including specification of paging and sorting by unique
|
|
||||||
identifier):
|
|
||||||
|
|
||||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART VaultJavaQueryExample5
|
|
||||||
:end-before: DOCEND VaultJavaQueryExample5
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
Troubleshooting
|
|
||||||
---------------
|
|
||||||
If the results your were expecting do not match actual returned query results we recommend you add an entry to your
|
|
||||||
``log4j2.xml`` configuration file to enable display of executed SQL statements::
|
|
||||||
|
|
||||||
<Logger name="org.hibernate.SQL" level="debug" additivity="false">
|
|
||||||
<AppenderRef ref="Console-Appender"/>
|
|
||||||
</Logger>
|
|
||||||
|
|
||||||
Behavioural notes
|
|
||||||
-----------------
|
|
||||||
1. ``TrackBy`` updates do not take into account the full criteria specification due to different and more restrictive
|
|
||||||
syntax in `observables <https://github.com/ReactiveX/RxJava/wiki>`_ filtering (vs full SQL-92 JDBC filtering as used
|
|
||||||
in snapshot views). Specifically, dynamic updates are filtered by ``contractStateType`` and ``stateType``
|
|
||||||
(UNCONSUMED, CONSUMED, ALL) only
|
|
||||||
2. ``QueryBy`` and ``TrackBy`` snapshot views using pagination may return different result sets as each paging request
|
|
||||||
is a separate SQL query on the underlying database, and it is entirely conceivable that state modifications are
|
|
||||||
taking place in between and/or in parallel to paging requests. When using pagination, always check the value of the
|
|
||||||
``totalStatesAvailable`` (from the ``Vault.Page`` result) and adjust further paging requests appropriately.
|
|
||||||
|
|
||||||
Other use case scenarios
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
For advanced use cases that require sophisticated pagination, sorting, grouping, and aggregation functions, it is
|
|
||||||
recommended that the CorDapp developer utilise one of the many proven frameworks that ship with this capability out of
|
|
||||||
the box. Namely, implementations of JPQL (JPA Query Language) such as Hibernate for advanced SQL access, and
|
|
||||||
Spring Data for advanced pagination and ordering constructs.
|
|
||||||
|
|
||||||
The Corda Tutorials provide examples satisfying these additional Use Cases:
|
|
||||||
|
|
||||||
1. Example CorDapp service using Vault API Custom Query to access attributes of IOU State
|
|
||||||
2. Example CorDapp service query extension executing Named Queries via JPQL_
|
|
||||||
3. `Advanced pagination <https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html>`_ queries using Spring Data JPA_
|
|
||||||
|
|
||||||
.. _JPQL: http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql
|
|
||||||
.. _JPA: https://docs.spring.io/spring-data/jpa/docs/current/reference/html
|
|
||||||
|
|
||||||
Mapping owning keys to external IDs
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
When creating new public keys via the ``KeyManagementService``, it is possible to create an association between the newly created public
|
|
||||||
key and an external ID. This, in effect, allows CorDapp developers to group state ownership/participation keys by an account ID.
|
|
||||||
|
|
||||||
.. note:: This only works with freshly generated public keys and *not* the node's legal identity key. If you require that the freshly
|
|
||||||
generated keys be for the node's identity then use ``PersistentKeyManagementService.freshKeyAndCert`` instead of ``freshKey``.
|
|
||||||
Currently, the generation of keys for other identities is not supported.
|
|
||||||
|
|
||||||
The code snippet below show how keys can be associated with an external ID by using the exposed JPA functionality:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
public AnonymousParty freshKeyForExternalId(UUID externalId, ServiceHub services) {
|
|
||||||
// Create a fresh key pair and return the public key.
|
|
||||||
AnonymousParty anonymousParty = freshKey();
|
|
||||||
// Associate the fresh key to an external ID.
|
|
||||||
services.withEntityManager(entityManager -> {
|
|
||||||
PersistentKeyManagementService.PublicKeyHashToExternalId mapping = PersistentKeyManagementService.PublicKeyHashToExternalId(externalId, anonymousParty.owningKey);
|
|
||||||
entityManager.persist(mapping);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
return anonymousParty;
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
fun freshKeyForExternalId(externalId: UUID, services: ServiceHub): AnonymousParty {
|
|
||||||
// Create a fresh key pair and return the public key.
|
|
||||||
val anonymousParty = freshKey()
|
|
||||||
// Associate the fresh key to an external ID.
|
|
||||||
services.withEntityManager {
|
|
||||||
val mapping = PersistentKeyManagementService.PublicKeyHashToExternalId(externalId, anonymousParty.owningKey)
|
|
||||||
persist(mapping)
|
|
||||||
}
|
|
||||||
return anonymousParty
|
|
||||||
}
|
|
||||||
|
|
||||||
As can be seen in the code snippet above, the ``PublicKeyHashToExternalId`` entity has been added to ``PersistentKeyManagementService``,
|
|
||||||
which allows you to associate your public keys with external IDs. So far, so good.
|
|
||||||
|
|
||||||
.. note:: Here, it is worth noting that we must map **owning keys** to external IDs, as opposed to **state objects**. This is because it
|
|
||||||
might be the case that a ``LinearState`` is owned by two public keys generated by the same node.
|
|
||||||
|
|
||||||
The intuition here is that when these public keys are used to own or participate in a state object, it is trivial to then associate those
|
|
||||||
states with a particular external ID. Behind the scenes, when states are persisted to the vault, the owning keys for each state are
|
|
||||||
persisted to a ``PersistentParty`` table. The ``PersistentParty`` table can be joined with the ``PublicKeyHashToExternalId`` table to create
|
|
||||||
a view which maps each state to one or more external IDs. The entity relationship diagram below helps to explain how this works.
|
|
||||||
|
|
||||||
.. image:: resources/state-to-external-id.png
|
|
||||||
|
|
||||||
When performing a vault query, it is now possible to query for states by external ID using the ``externalIds`` parameter in
|
|
||||||
``VaultQueryCriteria``.
|
|
@ -1,600 +0,0 @@
|
|||||||
.. highlight:: kotlin
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
||||||
|
|
||||||
Upgrading CorDapps to newer Platform Versions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
These notes provide instructions for upgrading your CorDapps from previous versions. Corda provides backwards compatibility for public,
|
|
||||||
non-experimental APIs that have been committed to. A list can be found in the :doc:`api-stability-guarantees` page.
|
|
||||||
|
|
||||||
This means that you can upgrade your node across versions *without recompiling or adjusting your CorDapps*. You just have to upgrade
|
|
||||||
your node and restart.
|
|
||||||
|
|
||||||
However, there are usually new features and other opt-in changes that may improve the security, performance or usability of your
|
|
||||||
application that are worth considering for any actively maintained software. This guide shows you how to upgrade your app to benefit
|
|
||||||
from the new features in the latest release.
|
|
||||||
|
|
||||||
.. warning:: The sample apps found in the Corda repository and the Corda samples repository are not intended to be used in production.
|
|
||||||
If you are using them you should re-namespace them to a package namespace you control, and sign/version them yourself.
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:depth: 3
|
|
||||||
|
|
||||||
Upgrading apps to Platform Version 5
|
|
||||||
====================================
|
|
||||||
|
|
||||||
This section provides instructions for upgrading your CorDapps from previous versions to take advantage of features and enhancements introduced
|
|
||||||
in platform version 5.
|
|
||||||
|
|
||||||
.. note:: If you are upgrading from a platform version older than 4, then the upgrade notes for upgrading to Corda 4 (below) also apply.
|
|
||||||
|
|
||||||
Step 1. Handle any source compatibility breaks (if using Kotlin)
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
||||||
The following code, which compiled in Platform Version 4, will not compile in Platform Version 5:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
data class Obligation(val amount: Amount<Currency>, val lender: AbstractParty, val borrower: AbstractParty)
|
|
||||||
|
|
||||||
val (lenderId, borrowerId) = if (anonymous) {
|
|
||||||
val anonymousIdentitiesResult = subFlow(SwapIdentitiesFlow(lenderSession))
|
|
||||||
Pair(anonymousIdentitiesResult[lenderSession.counterparty]!!, anonymousIdentitiesResult[ourIdentity]!!)
|
|
||||||
} else {
|
|
||||||
Pair(lender, ourIdentity)
|
|
||||||
}
|
|
||||||
|
|
||||||
val obligation = Obligation(100.dollars, lenderId, borrowerId)
|
|
||||||
|
|
||||||
Compiling this code against Platform Version 5 will result in the following error:
|
|
||||||
|
|
||||||
``Type mismatch: inferred type is Any but AbstractParty was expected``
|
|
||||||
|
|
||||||
The issue here is that a new ``Destination`` interface introduced in Platform Version 5 can cause type inference failures when a variable is
|
|
||||||
used as an ``AbstractParty`` but has an actual value that is one of ``Party`` or ``AnonymousParty``. These subclasses
|
|
||||||
implement ``Destination``, while the superclass does not. Kotlin must pick a type for the variable, and so chooses the most specific
|
|
||||||
ancestor of both ``AbstractParty`` and ``Destination``. This is ``Any``, which is not a valid type for use as an ``AbstractParty`` later.
|
|
||||||
(For more information on ``Destination``, see the :doc:`changelog` for Platform Version 5, or the KDocs for the interface
|
|
||||||
`here <https://docs.corda.net/head/api/kotlin/corda/net.corda.core.flows/-destination.html>`__)
|
|
||||||
|
|
||||||
Note that this is a Kotlin-specific issue. Java can instead choose ``? extends AbstractParty & Destination`` here, which can later be used
|
|
||||||
as ``AbstractParty``.
|
|
||||||
|
|
||||||
To fix this, an explicit type hint must be provided to the compiler:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
data class Obligation(val amount: Amount<Currency>, val lender: AbstractParty, val borrower: AbstractParty)
|
|
||||||
|
|
||||||
val (lenderId, borrowerId) = if (anonymous) {
|
|
||||||
val anonymousIdentitiesResult = subFlow(SwapIdentitiesFlow(lenderSession))
|
|
||||||
Pair(anonymousIdentitiesResult[lenderSession.counterparty]!!, anonymousIdentitiesResult[ourIdentity]!!)
|
|
||||||
} else {
|
|
||||||
// This Pair now provides a type hint to the compiler
|
|
||||||
Pair<AbstractParty, AbstractParty>(lender, ourIdentity)
|
|
||||||
}
|
|
||||||
|
|
||||||
val obligation = Obligation(100.dollars, lenderId, borrowerId)
|
|
||||||
|
|
||||||
This stops type inference from occurring and forces the variable to be of type ``AbstractParty``.
|
|
||||||
|
|
||||||
.. _platform_version_5_gradle_changes:
|
|
||||||
|
|
||||||
Step 2. Update Gradle version and associated dependencies
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
Platform Version 5 requires Gradle 5.4 to build. If you use the Gradle wrapper, you can upgrade by running:
|
|
||||||
|
|
||||||
.. code:: shell
|
|
||||||
|
|
||||||
./gradlew wrapper --gradle-version 5.4.1
|
|
||||||
|
|
||||||
Otherwise, upgrade your installed copy in the usual manner for your operating system.
|
|
||||||
|
|
||||||
Additionally, you'll need to add https://repo.gradle.org/gradle/libs-releases as a repository to your project, in order to pick up the
|
|
||||||
`gradle-api-tooling` dependency. You can do this by adding the following to the repositories in your Gradle file:
|
|
||||||
|
|
||||||
.. code-block:: groovy
|
|
||||||
|
|
||||||
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
|
|
||||||
|
|
||||||
Upgrading apps to Platform Version 4
|
|
||||||
====================================
|
|
||||||
|
|
||||||
This section provides instructions for upgrading your CorDapps from previous versions to platform version 4.
|
|
||||||
|
|
||||||
Step 1. Switch any RPC clients to use the new RPC library
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
Although the RPC API is backwards compatible with Corda 3, the RPC wire protocol isn't. Therefore RPC clients like web servers need to be
|
|
||||||
updated in lockstep with the node to use the new version of the RPC library. Corda 4 delivers RPC wire stability and therefore in future you
|
|
||||||
will be able to update the node and apps without updating RPC clients.
|
|
||||||
|
|
||||||
.. _cordapp_upgrade_version_numbers_ref:
|
|
||||||
|
|
||||||
Step 2. Adjust the version numbers in your Gradle build files
|
|
||||||
-------------------------------------------------------------
|
|
||||||
|
|
||||||
Alter the versions you depend on in your Gradle file like so:
|
|
||||||
|
|
||||||
.. code-block:: groovy
|
|
||||||
|
|
||||||
ext.corda_release_version = '|corda_version|'
|
|
||||||
ext.corda_gradle_plugins_version = '|gradle_plugins_version|'
|
|
||||||
ext.kotlin_version = '|kotlin_version|'
|
|
||||||
ext.quasar_version = '|quasar_version|'
|
|
||||||
|
|
||||||
.. note:: You may wish to update your kotlinOptions to use language level 1.2, to benefit from the new features. Apps targeting Corda 4
|
|
||||||
may not at this time use Kotlin 1.3, as it was released too late in the development cycle
|
|
||||||
for us to risk an upgrade. Sorry! Future work on app isolation will make it easier for apps to use newer Kotlin versions than
|
|
||||||
the node itself uses.
|
|
||||||
|
|
||||||
You should also ensure you're using Gradle 4.10 (but not 5). If you use the Gradle wrapper, run:
|
|
||||||
|
|
||||||
.. code:: shell
|
|
||||||
|
|
||||||
./gradlew wrapper --gradle-version 4.10.3
|
|
||||||
|
|
||||||
Otherwise just upgrade your installed copy in the usual manner for your operating system.
|
|
||||||
|
|
||||||
.. note:: Platform Version 5 requires a different version of Gradle, so if you're intending to upgrade past Platform Version 4 you may wish
|
|
||||||
to skip updating Gradle here and upgrade directly to the version required by Platform Version 5. You'll still need to alter the version
|
|
||||||
numbers in your Gradle file as shown in this section. See :ref:`platform_version_5_gradle_changes`
|
|
||||||
|
|
||||||
Step 3. Update your Gradle build file
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
There are several adjustments that are beneficial to make to your Gradle build file, beyond simply incrementing the versions
|
|
||||||
as described in step 1.
|
|
||||||
|
|
||||||
**Provide app metadata.** This is used by the Corda Gradle build plugin to populate your app JAR with useful information.
|
|
||||||
It should look like this:
|
|
||||||
|
|
||||||
.. code-block:: groovy
|
|
||||||
|
|
||||||
cordapp {
|
|
||||||
targetPlatformVersion 4
|
|
||||||
minimumPlatformVersion 4
|
|
||||||
contract {
|
|
||||||
name "MegaApp Contracts"
|
|
||||||
vendor "MegaCorp"
|
|
||||||
licence "A liberal, open source licence"
|
|
||||||
versionId 1
|
|
||||||
}
|
|
||||||
workflow {
|
|
||||||
name "MegaApp flows"
|
|
||||||
vendor "MegaCorp"
|
|
||||||
licence "A really expensive proprietary licence"
|
|
||||||
versionId 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.. important:: Watch out for the UK spelling of the word licence (with a c).
|
|
||||||
|
|
||||||
Name, vendor and licence can be set to any string you like, they don't have to be Corda identities.
|
|
||||||
|
|
||||||
Target versioning is a new concept introduced in Corda 4. Learn more by reading :doc:`versioning`.
|
|
||||||
Setting a target version of 4 opts in to changes that might not be 100% backwards compatible, such as
|
|
||||||
API semantics changes or disabling workarounds for bugs that may be in your apps, so by doing this you
|
|
||||||
are promising that you have thoroughly tested your app on the new version. Using a high target version is
|
|
||||||
a good idea because some features and improvements are only available to apps that opt in.
|
|
||||||
|
|
||||||
The minimum platform version is the platform version of the node that you require, so if you
|
|
||||||
start using new APIs and features in Corda 4, you should set this to 4. Unfortunately Corda 3 and below
|
|
||||||
do not know about this metadata and don't check it, so your app will still be loaded in such nodes and
|
|
||||||
may exhibit undefined behaviour at runtime. However it's good to get in the habit of setting this
|
|
||||||
properly for future releases.
|
|
||||||
|
|
||||||
.. note:: Whilst it's currently a convention that Corda releases have the platform version number as their
|
|
||||||
major version i.e. Corda 3.3 implements platform version 3, this is not actually required and may in
|
|
||||||
future not hold true. You should know the platform version of the node releases you want to target.
|
|
||||||
|
|
||||||
The new ``versionId`` number is a version code for **your** app, and is unrelated to Corda's own versions.
|
|
||||||
It is currently used for informative purposes only.
|
|
||||||
|
|
||||||
**Split your app into contract and workflow JARs.** The duplication between ``contract`` and ``workflow`` blocks exists because you should split your app into
|
|
||||||
two separate JARs/modules, one that contains on-ledger validation code like states and contracts, and one
|
|
||||||
for the rest (called by convention the "workflows" module although it can contain a lot more than just flows:
|
|
||||||
services would also go here, for instance). For simplicity, here we use one JAR for both, but this is in
|
|
||||||
general an anti-pattern and can result in your flow logic code being sent over the network to arbitrary
|
|
||||||
third party peers, even though they don't need it.
|
|
||||||
|
|
||||||
In future, the version ID attached to the workflow JAR will also be used to help implement smoother upgrade
|
|
||||||
and migration features. You may directly reference the gradle version number of your app when setting the
|
|
||||||
CorDapp specific versionId identifiers if this follows the convention of always being a whole number
|
|
||||||
starting from 1.
|
|
||||||
|
|
||||||
If you use the finance demo app, you should adjust your dependencies so you depend on the finance-contracts
|
|
||||||
and finance-workflows artifacts from your own contract and workflow JAR respectively.
|
|
||||||
|
|
||||||
Step 4. Remove any custom configuration from the node.conf
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
CorDapps can no longer access custom configuration items in the ``node.conf`` file. Any custom CorDapp configuration should be added to a
|
|
||||||
CorDapp configuration file. The Node's configuration will not be accessible. CorDapp configuration files should be placed in the
|
|
||||||
`config` subdirectory of the Node's `cordapps` folder. The name of the file should match the name of the JAR of the CorDapp (eg; if your
|
|
||||||
CorDapp is called ``hello-0.1.jar`` the configuration file needed would be ``cordapps/config/hello-0.1.conf``).
|
|
||||||
|
|
||||||
If you are using the ``extraConfig`` of a ``node`` in the ``deployNodes`` Gradle task to populate custom configuration for testing, you will need
|
|
||||||
to make the following change so that:
|
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|
||||||
node {
|
|
||||||
name "O=Bank A,L=London,C=GB"c
|
|
||||||
...
|
|
||||||
extraConfig = [ 'some.extra.config' : '12345' ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Would become:
|
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|
||||||
node {
|
|
||||||
name "O=Bank A,L=London,C=GB"c
|
|
||||||
...
|
|
||||||
projectCordapp {
|
|
||||||
config "some.extra.config=12345"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
See :ref:`cordapp_configuration_files_ref` for more information.
|
|
||||||
|
|
||||||
.. _cordapp_upgrade_finality_flow_ref:
|
|
||||||
|
|
||||||
Step 5. Security: Upgrade your use of FinalityFlow
|
|
||||||
--------------------------------------------------
|
|
||||||
|
|
||||||
The previous ``FinalityFlow`` API is insecure. It doesn't have a receive flow, so requires counterparty nodes to accept any and
|
|
||||||
all signed transactions that are sent to it, without checks. It is **highly** recommended that existing CorDapps migrate
|
|
||||||
away to the new API, as otherwise things like business network membership checks won't be reliably enforced.
|
|
||||||
|
|
||||||
The flows that make use of ``FinalityFlow`` in a CorDapp can be classified in the following 2 basic categories:
|
|
||||||
|
|
||||||
* **non-initiating flows**: these are flows that finalise a transaction without the involvement of a counterpart flow at all.
|
|
||||||
* **initiating flows**: these are flows that initiate a counterpart (responder) flow.
|
|
||||||
|
|
||||||
There is a main difference between these 2 different categories, which is relevant to how the CorDapp can be upgraded.
|
|
||||||
The second category of flows can be upgraded to use the new ``FinalityFlow`` in a backwards compatible way, which means the upgraded CorDapp can be deployed at the various nodes using a *rolling deployment*.
|
|
||||||
On the other hand, the first category of flows cannot be upgraded to the new ``FinalityFlow`` in a backwards compatible way, so the changes to these flows need to be deployed simultaneously at all the nodes, using a *lockstep deployment*.
|
|
||||||
|
|
||||||
.. note:: A *lockstep deployment* is one, where all the involved nodes are stopped, upgraded to the new version of the CorDapp and then re-started.
|
|
||||||
As a result, there can't be any nodes running different versions of the CorDapp at any time.
|
|
||||||
A *rolling deployment* is one, where every node can be stopped, upgraded to the new version of the CorDapp and re-started independently and on its own pace.
|
|
||||||
As a result, there can be nodes running different versions of the CorDapp and transact with each other successfully.
|
|
||||||
|
|
||||||
The upgrade is a three step process:
|
|
||||||
|
|
||||||
1. Change the flow that calls ``FinalityFlow``.
|
|
||||||
2. Change or create the flow that will receive the finalised transaction.
|
|
||||||
3. Make sure your application's minimum and target version numbers are both set to 4 (see :ref:`cordapp_upgrade_version_numbers_ref`).
|
|
||||||
|
|
||||||
Upgrading a non-initiating flow
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
As an example, let's take a very simple flow that finalises a transaction without the involvement of a counterpart flow:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/kotlin/FinalityFlowMigration.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART SimpleFlowUsingOldApi
|
|
||||||
:end-before: DOCEND SimpleFlowUsingOldApi
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/java/net/corda/docs/java/FinalityFlowMigration.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART SimpleFlowUsingOldApi
|
|
||||||
:end-before: DOCEND SimpleFlowUsingOldApi
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
To use the new API, this flow needs to be annotated with ``InitiatingFlow`` and a ``FlowSession`` to the participant(s) of the transaction must be
|
|
||||||
passed to ``FinalityFlow`` :
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/kotlin/FinalityFlowMigration.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART SimpleFlowUsingNewApi
|
|
||||||
:end-before: DOCEND SimpleFlowUsingNewApi
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/java/net/corda/docs/java/FinalityFlowMigration.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART SimpleFlowUsingNewApi
|
|
||||||
:end-before: DOCEND SimpleFlowUsingNewApi
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
If there are more than one transaction participants then a session to each one must be initiated, excluding the local party
|
|
||||||
and the notary.
|
|
||||||
|
|
||||||
A responder flow has to be introduced, which will automatically run on the other participants' nodes, which will call ``ReceiveFinalityFlow``
|
|
||||||
to record the finalised transaction:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/kotlin/FinalityFlowMigration.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART SimpleNewResponderFlow
|
|
||||||
:end-before: DOCEND SimpleNewResponderFlow
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/java/net/corda/docs/java/FinalityFlowMigration.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART SimpleNewResponderFlow
|
|
||||||
:end-before: DOCEND SimpleNewResponderFlow
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
.. note:: As described above, all the nodes in your business network will need the new CorDapp, otherwise they won't know how to receive the transaction. **This
|
|
||||||
includes nodes which previously didn't have the old CorDapp.** If a node is sent a transaction and it doesn't have the new CorDapp loaded
|
|
||||||
then simply restart it with the CorDapp and the transaction will be recorded.
|
|
||||||
|
|
||||||
Upgrading an initiating flow
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
For flows which are already initiating counterpart flows then it's a matter of using the existing flow session.
|
|
||||||
Note however, the new ``FinalityFlow`` is inlined and so the sequence of sends and receives between the two flows will
|
|
||||||
change and will be incompatible with your current flows. You can use the flow version API to write your flows in a
|
|
||||||
backwards compatible manner.
|
|
||||||
|
|
||||||
Here's what an upgraded initiating flow may look like:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/kotlin/FinalityFlowMigration.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART ExistingInitiatingFlow
|
|
||||||
:end-before: DOCEND ExistingInitiatingFlow
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/java/net/corda/docs/java/FinalityFlowMigration.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART ExistingInitiatingFlow
|
|
||||||
:end-before: DOCEND ExistingInitiatingFlow
|
|
||||||
:dedent: 4
|
|
||||||
|
|
||||||
For the responder flow, insert a call to ``ReceiveFinalityFlow`` at the location where it's expecting to receive the
|
|
||||||
finalised transaction. If the initiator is written in a backwards compatible way then so must the responder.
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/kotlin/FinalityFlowMigration.kt
|
|
||||||
:language: kotlin
|
|
||||||
:start-after: DOCSTART ExistingResponderFlow
|
|
||||||
:end-before: DOCEND ExistingResponderFlow
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/java/net/corda/docs/java/FinalityFlowMigration.java
|
|
||||||
:language: java
|
|
||||||
:start-after: DOCSTART ExistingResponderFlow
|
|
||||||
:end-before: DOCEND ExistingResponderFlow
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
You may already be using ``waitForLedgerCommit`` in your responder flow for the finalised transaction to appear in the local node's vault.
|
|
||||||
Now that it's calling ``ReceiveFinalityFlow``, which effectively does the same thing, this is no longer necessary. The call to
|
|
||||||
``waitForLedgerCommit`` should be removed.
|
|
||||||
|
|
||||||
Step 6. Security: Upgrade your use of SwapIdentitiesFlow
|
|
||||||
--------------------------------------------------------
|
|
||||||
|
|
||||||
The :ref:`confidential_identities_ref` API is experimental in Corda 3 and remains so in Corda 4. In this release, the ``SwapIdentitiesFlow``
|
|
||||||
has been adjusted in the same way as ``FinalityFlow`` above, to close problems with confidential identities being injectable into a node
|
|
||||||
outside of other flow context. Old code will still work, but it is recommended to adjust your call sites so a session is passed into
|
|
||||||
the ``SwapIdentitiesFlow``.
|
|
||||||
|
|
||||||
Step 7. Possibly, adjust test code
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
``MockNodeParameters`` and functions creating it no longer use a lambda expecting a ``NodeConfiguration`` object.
|
|
||||||
Use a ``MockNetworkConfigOverrides`` object instead. This is an API change we regret, but unfortunately in Corda 3 we accidentally exposed
|
|
||||||
large amounts of the node internal code through this one API entry point. We have now insulated the test API from node internals and
|
|
||||||
reduced the exposure.
|
|
||||||
|
|
||||||
If you are constructing a MockServices for testing contracts, and your contract uses the Cash contract from the finance app, you
|
|
||||||
now need to explicitly add ``net.corda.finance.contracts`` to the list of ``cordappPackages``. This is a part of the work to disentangle
|
|
||||||
the finance app (which is really a demo app) from the Corda internals. Example:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val ledgerServices = MockServices(
|
|
||||||
listOf("net.corda.examples.obligation", "net.corda.testing.contracts"),
|
|
||||||
initialIdentity = TestIdentity(CordaX500Name("TestIdentity", "", "GB")),
|
|
||||||
identityService = makeTestIdentityService()
|
|
||||||
)
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
MockServices ledgerServices = new MockServices(
|
|
||||||
Arrays.asList("net.corda.examples.obligation", "net.corda.testing.contracts"),
|
|
||||||
new TestIdentity(new CordaX500Name("TestIdentity", "", "GB")),
|
|
||||||
makeTestIdentityService()
|
|
||||||
);
|
|
||||||
|
|
||||||
becomes:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val ledgerServices = MockServices(
|
|
||||||
listOf("net.corda.examples.obligation", "net.corda.testing.contracts", "net.corda.finance.contracts"),
|
|
||||||
initialIdentity = TestIdentity(CordaX500Name("TestIdentity", "", "GB")),
|
|
||||||
identityService = makeTestIdentityService()
|
|
||||||
)
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
MockServices ledgerServices = new MockServices(
|
|
||||||
Arrays.asList("net.corda.examples.obligation", "net.corda.testing.contracts", "net.corda.finance.contracts"),
|
|
||||||
new TestIdentity(new CordaX500Name("TestIdentity", "", "GB")),
|
|
||||||
makeTestIdentityService()
|
|
||||||
);
|
|
||||||
|
|
||||||
You may need to use the new ``TestCordapp`` API when testing with the node driver or mock network, especially if you decide to stick with the
|
|
||||||
pre-Corda 4 ``FinalityFlow`` API. The previous way of pulling in CorDapps into your tests (i.e. via using the ``cordappPackages`` parameter) does not honour CorDapp versioning.
|
|
||||||
The new API ``TestCordapp.findCordapp()`` discovers the CorDapps that contain the provided packages scanning the classpath, so you have to ensure that the classpath the tests are running under contains either the CorDapp ``.jar`` or (if using Gradle) the relevant Gradle sub-project.
|
|
||||||
In the first case, the versioning information in the CorDapp ``.jar`` file will be maintained. In the second case, the versioning information will be retrieved from the Gradle ``cordapp`` task.
|
|
||||||
For example, if you are using ``MockNetwork`` for your tests, the following code:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val mockNetwork = MockNetwork(
|
|
||||||
cordappPackages = listOf("net.corda.examples.obligation", "net.corda.finance.contracts"),
|
|
||||||
notarySpecs = listOf(MockNetworkNotarySpec(notary))
|
|
||||||
)
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
MockNetwork mockNetwork = new MockNetwork(
|
|
||||||
Arrays.asList("net.corda.examples.obligation", "net.corda.finance.contracts"),
|
|
||||||
new MockNetworkParameters().withNotarySpecs(Arrays.asList(new MockNetworkNotarySpec(notary)))
|
|
||||||
);
|
|
||||||
|
|
||||||
would need to be transformed into:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
val mockNetwork = MockNetwork(
|
|
||||||
MockNetworkParameters(
|
|
||||||
cordappsForAllNodes = listOf(
|
|
||||||
TestCordapp.findCordapp("net.corda.examples.obligation.contracts"),
|
|
||||||
TestCordapp.findCordapp("net.corda.examples.obligation.flows")
|
|
||||||
),
|
|
||||||
notarySpecs = listOf(MockNetworkNotarySpec(notary))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
MockNetwork mockNetwork = new MockNetwork(
|
|
||||||
new MockNetworkParameters(
|
|
||||||
Arrays.asList(
|
|
||||||
TestCordapp.findCordapp("net.corda.examples.obligation.contracts"),
|
|
||||||
TestCordapp.findCordapp("net.corda.examples.obligation.flows")
|
|
||||||
)
|
|
||||||
).withNotarySpecs(Arrays.asList(new MockNetworkNotarySpec(notary)))
|
|
||||||
);
|
|
||||||
|
|
||||||
Note that every package should exist in only one CorDapp, otherwise the discovery process won't be able to determine which one to use and you will most probably see an exception telling you ``There is more than one CorDapp containing the package``.
|
|
||||||
For instance, if you have 2 CorDapps containing the packages ``net.corda.examples.obligation.contracts`` and ``net.corda.examples.obligation.flows``, you will get this error if you specify the package ``net.corda.examples.obligation``.
|
|
||||||
|
|
||||||
|
|
||||||
.. note:: If you have any CorDapp code (e.g. flows/contracts/states) that is only used by the tests and located in the same test module, it won't be discovered now.
|
|
||||||
You will need to move them in the main module of one of your CorDapps or create a new, separate CorDapp for them, in case you don't want this code to live inside your production CorDapps.
|
|
||||||
|
|
||||||
Step 8. Security: Add BelongsToContract annotations
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
In versions of the platform prior to v4, it was the responsibility of contract and flow logic to ensure that ``TransactionState`` objects
|
|
||||||
contained the correct class name of the expected contract class. If these checks were omitted, it would be possible for a malicious counterparty
|
|
||||||
to construct a transaction containing e.g. a cash state governed by a commercial paper contract. The contract would see that there were no
|
|
||||||
commercial paper states in a transaction and do nothing, i.e. accept.
|
|
||||||
|
|
||||||
In Corda 4 the platform takes over this responsibility from the app, if the app has a target version of 4 or higher. A state is expected
|
|
||||||
to be governed by a contract that is either:
|
|
||||||
|
|
||||||
1. The outer class of the state class, if the state is an inner class of a contract. This is a common design pattern.
|
|
||||||
2. Annotated with ``@BelongsToContract`` which specifies the contract class explicitly.
|
|
||||||
|
|
||||||
Learn more by reading :ref:`contract_state_agreement`. If an app targets Corda 3 or lower (i.e. does not specify a target version),
|
|
||||||
states that point to contracts outside their package will trigger a log warning but validation will proceed.
|
|
||||||
|
|
||||||
Step 9. Learn about signature constraints and JAR signing
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
:ref:`signature_constraints` are a new data model feature introduced in Corda 4. They make it much easier to
|
|
||||||
deploy application upgrades smoothly and in a decentralised manner. Signature constraints are the new default mode for CorDapps, and
|
|
||||||
the act of upgrading your app to use the version 4 Gradle plugins will result in your app being automatically signed, and new states
|
|
||||||
automatically using new signature constraints selected automatically based on these signing keys.
|
|
||||||
|
|
||||||
You can read more about signature constraints and what they do in :doc:`api-contract-constraints`. The ``TransactionBuilder`` class will
|
|
||||||
automatically use them if your application JAR is signed. **We recommend all JARs are signed**. To learn how to sign your JAR files, read
|
|
||||||
:ref:`cordapp_build_system_signing_cordapp_jar_ref`. In dev mode, all JARs are signed by developer certificates. If a JAR that was signed
|
|
||||||
with developer certificates is deployed to a production node, the node will refuse to start. Therefore to deploy apps built for Corda 4
|
|
||||||
to production you will need to generate signing keys and integrate them with the build process.
|
|
||||||
|
|
||||||
.. note:: Please read the :doc:`cordapp-constraint-migration` guide to understand how to upgrade CorDapps to use Corda 4 signature constraints and consume
|
|
||||||
existing states on ledger issued with older constraint types (e.g. Corda 3.x states issued with **hash** or **CZ whitelisted** constraints).
|
|
||||||
|
|
||||||
Step 10. Security: Package namespace handling
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
Almost no apps will be affected by these changes, but they're important to know about.
|
|
||||||
|
|
||||||
There are two improvements to how Java package protection is handled in Corda 4:
|
|
||||||
|
|
||||||
1. Package sealing
|
|
||||||
2. Package namespace ownership
|
|
||||||
|
|
||||||
**Sealing.** App isolation has been improved. Version 4 of the finance CorDapps (*corda-finance-contracts.jar*, *corda-finance-workflows.jar*) is now built as a set of sealed and
|
|
||||||
signed JAR files. This means classes in your own CorDapps cannot be placed under the following package namespace: ``net.corda.finance``
|
|
||||||
|
|
||||||
In the unlikely event that you were injecting code into ``net.corda.finance.*`` package namespaces from your own apps, you will need to move them
|
|
||||||
into a new package, e.g. ``net/corda/finance/flows/MyClass.java`` can be moved to ``com/company/corda/finance/flows/MyClass.java``.
|
|
||||||
As a consequence your classes are no longer able to access non-public members of finance CorDapp classes.
|
|
||||||
|
|
||||||
When signing your JARs for Corda 4, your own apps will also become sealed, meaning other JARs cannot place classes into your own packages.
|
|
||||||
This is a security upgrade that ensures package-private visibility in Java code works correctly. If other apps could define classes in your own
|
|
||||||
packages, they could call package-private methods, which may not be expected by the developers.
|
|
||||||
|
|
||||||
**Namespace ownership.** This part is only relevant if you are joining a production compatibility zone. You may wish to contact your zone operator
|
|
||||||
and request ownership of your root package namespaces (e.g. ``com.megacorp.*``), with the signing keys you will be using to sign your app JARs.
|
|
||||||
The zone operator can then add your signing key to the network parameters, and prevent attackers defining types in your own package namespaces.
|
|
||||||
Whilst this feature is optional and not strictly required, it may be helpful to block attacks at the boundaries of a Corda based application
|
|
||||||
where type names may be taken "as read".
|
|
||||||
|
|
||||||
Step 11. Consider adding extension points to your flows
|
|
||||||
-------------------------------------------------------
|
|
||||||
|
|
||||||
In Corda 4 it is possible for flows in one app to subclass and take over flows from another. This allows you to create generic, shared
|
|
||||||
flow logic that individual users can customise at pre-agreed points (protected methods). For example, a site-specific app could be developed
|
|
||||||
that causes transaction details to be converted to a PDF and sent to a particular printer. This would be an inappropriate feature to put
|
|
||||||
into shared business logic, but it makes perfect sense to put into a user-specific app they developed themselves.
|
|
||||||
|
|
||||||
If your flows could benefit from being extended in this way, read ":doc:`flow-overriding`" to learn more.
|
|
||||||
|
|
||||||
Step 12. Possibly update vault state queries
|
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
In Corda 4 queries made on a node's vault can filter by the relevancy of those states to the node. As this functionality does not exist in
|
|
||||||
Corda 3, apps will continue to receive all states in any vault queries. However, it may make sense to migrate queries expecting just those states relevant
|
|
||||||
to the node in question to query for only relevant states. See :doc:`api-vault-query` for more details on how to do this. Not doing this
|
|
||||||
may result in queries returning more states than expected if the node is using observer functionality (see ":doc:`tutorial-observer-nodes`").
|
|
||||||
|
|
||||||
Step 13. Explore other new features that may be useful
|
|
||||||
------------------------------------------------------
|
|
||||||
|
|
||||||
Corda 4 adds several new APIs that help you build applications. Why not explore:
|
|
||||||
|
|
||||||
* The `new withEntityManager API <api/javadoc/net/corda/core/node/ServiceHub.html#withEntityManager-block->`_ for using JPA inside your flows and services.
|
|
||||||
* :ref:`reference_states`, that let you use an input state without consuming it.
|
|
||||||
* :ref:`state_pointers`, that make it easier to 'point' to one state from another and follow the latest version of a linear state.
|
|
||||||
|
|
||||||
Please also read the :doc:`CorDapp Upgradeability Guarantees <cordapp-upgradeability>` associated with CorDapp upgrading.
|
|
||||||
|
|
||||||
Step 14. Possibly update your checked in quasar.jar
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
If your project is based on one of the official cordapp templates, it is likely you have a ``lib/quasar.jar`` checked in. It is worth noting
|
|
||||||
that you only use this if you use the JUnit runner in IntelliJ. In the latest release of the cordapp templates, this directory has
|
|
||||||
been removed.
|
|
||||||
|
|
||||||
You have some choices here:
|
|
||||||
|
|
||||||
* Upgrade your ``quasar.jar`` to ``|quasar_version|``
|
|
||||||
* Delete your ``lib`` directory and switch to using the Gradle test runner
|
|
||||||
|
|
||||||
Instructions for both options can be found in :ref:`Running tests in Intellij <tutorial_cordapp_running_tests_intellij>`.
|
|
@ -1,122 +0,0 @@
|
|||||||
Deploying Corda to Corda Testnet from an AWS Cloud Platform VM
|
|
||||||
==============================================================
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
This document explains how to deploy a Corda node to AWS that can connect directly to the Corda Testnet.
|
|
||||||
A self service download link can be obtained from https://marketplace.r3.com/network/testnet. This
|
|
||||||
document will describe how to set up a virtual machine on the AWS
|
|
||||||
Cloud Platform to deploy your pre-configured Corda node and automatically connnect
|
|
||||||
to Testnet.
|
|
||||||
|
|
||||||
Pre-requisites
|
|
||||||
--------------
|
|
||||||
* Ensure you have a registered Amazon AWS account which can create virtual machines and you are logged on to the AWS console: https://console.aws.amazon.com.
|
|
||||||
|
|
||||||
|
|
||||||
Deploy Corda node
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Browse to https://console.aws.amazon.com and log in with your AWS account.
|
|
||||||
|
|
||||||
|
|
||||||
**STEP 1: Launch a new virtual machine**
|
|
||||||
|
|
||||||
Click on Launch a virtual machine with EC2.
|
|
||||||
|
|
||||||
.. image:: resources/aws-launch.png
|
|
||||||
|
|
||||||
In the quick start wizard scroll down and select the most recent Ubuntu machine image as the Amazon Machine Image (AMI).
|
|
||||||
|
|
||||||
.. image:: resources/aws_select_ubuntu.png
|
|
||||||
|
|
||||||
Select the instance type (for example t2.xlarge).
|
|
||||||
|
|
||||||
.. image:: resources/aws-instance-type.png
|
|
||||||
|
|
||||||
Configure a couple of other settings before we review and launch
|
|
||||||
|
|
||||||
Under the storage tab (Step 4) increase the storage to 40GB:
|
|
||||||
|
|
||||||
.. image:: resources/aws-storage.png
|
|
||||||
|
|
||||||
Configure the security group (Step 6) to open the firewall ports which Corda uses.
|
|
||||||
|
|
||||||
.. image:: resources/aws-firewall.png
|
|
||||||
|
|
||||||
Add a firewall rule for port range 10002-10003 and allow connection from Anywhere. Add another rule for the webserver on port 8080.
|
|
||||||
|
|
||||||
Click on the Review and Launch button then if everything looks ok click Launch.
|
|
||||||
|
|
||||||
You will be prompted to set up keys to securely access the VM remotely over ssh. Select "Create a new key pair" from the drop down and enter a name for the key file. Click download to get the keys and keep them safe on your local machine.
|
|
||||||
|
|
||||||
.. note:: These keys are just for connecting to your VM and are separate from the keys Corda will use to sign transactions. These keys will be generated as part of the download bundle.
|
|
||||||
|
|
||||||
.. image:: resources/aws-keys.png
|
|
||||||
|
|
||||||
Click "Launch Instances".
|
|
||||||
|
|
||||||
Click on the link to go to the Instances pages in the AWS console where after a few minutes you will be able to see your instance running.
|
|
||||||
|
|
||||||
.. image:: resources/aws-instances.png
|
|
||||||
|
|
||||||
**STEP 2: Set up static IP address**
|
|
||||||
|
|
||||||
On AWS a permanent IP address is called an Elastic IP. Click on the
|
|
||||||
"Elastic IP" link in the navigation panel on the left hand side of the console and then click on "Allocate new address":
|
|
||||||
|
|
||||||
.. image:: resources/aws-elastic.png
|
|
||||||
|
|
||||||
Follow the form then once the address is allocated click on "Actions"
|
|
||||||
then "Associate address":
|
|
||||||
|
|
||||||
.. image:: resources/aws-elastic-actions.png
|
|
||||||
|
|
||||||
Then select the instance you created for your Corda node to attach the
|
|
||||||
IP address to.
|
|
||||||
|
|
||||||
**STEP 3: Connect to your VM and set up the environment**
|
|
||||||
|
|
||||||
In the instances console click on "Connect" and follow the instructions to connect to your instance using ssh.
|
|
||||||
|
|
||||||
.. image:: resources/aws-instances-connect.png
|
|
||||||
|
|
||||||
.. image:: resources/aws-connect.png
|
|
||||||
|
|
||||||
|
|
||||||
**STEP 4: Download and set up your Corda node**
|
|
||||||
|
|
||||||
Now your AWS environment is configured you can switch back to the Testnet
|
|
||||||
web application and click on the copy to clipboard button to get a one
|
|
||||||
time installation script.
|
|
||||||
|
|
||||||
.. note:: If you have not already set up your account on Testnet then please visit https://marketplace.r3.com/network/testnet and sign up.
|
|
||||||
|
|
||||||
.. image:: resources/testnet-platform.png
|
|
||||||
|
|
||||||
You can generate as many Testnet identites as you like by refreshing
|
|
||||||
this page to generate a new one time link.
|
|
||||||
|
|
||||||
In the terminal of your cloud instance paste the command you just copied to install and run
|
|
||||||
your unique Corda instance on that instance:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
sudo ONE_TIME_DOWNLOAD_KEY=YOUR_UNIQUE_DOWNLOAD_KEY_HERE bash -c "$(curl -L https://onboarder.prod.ws.r3.com/api/user/node/TESTNET/install.sh)"
|
|
||||||
|
|
||||||
.. warning:: This command will execute the install script as ROOT on your cloud instance. You may wish to examine the script prior to executing it on your machine.
|
|
||||||
|
|
||||||
You can follow the progress of the installation by typing the following command in your terminal:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
tail -f /opt/corda/logs/node-<VM-NAME>.log
|
|
||||||
|
|
||||||
|
|
||||||
Testing your deployment
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
To test your deployment is working correctly follow the instructions in :doc:`testnet-explorer-corda` to set up the Finance CorDapp and issue cash to a counterparty.
|
|
||||||
|
|
||||||
This will also demonstrate how to install a custom CorDapp.
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user