Merge remote-tracking branch 'remotes/open/master' into parkri-os-merge-20180814-1

# Conflicts:
#	CONTRIBUTORS.md
#	constants.properties
#	docs/source/index.rst
#	docs/source/upgrading-cordapps.rst
#	node/build.gradle
This commit is contained in:
rick.parker 2018-08-14 11:26:52 +01:00
commit 237c133804
35 changed files with 753 additions and 156 deletions

View File

@ -0,0 +1,205 @@
# List of Contributors
We'd like to thank the following people for contributing to Corda, either by
contributing to the design of Corda during the architecture review sessions of the
R3 Architecture Working Group and during design reviews since Corda has been
open-sourced, or by contributing code via pull requests. Some people have
moved to a different organisation since their contribution. Please forgive any
omissions, and create a pull request, or email <james@r3.com>, if you wish to
see changes to this list.
* acetheultimate
* Adrian Fletcher (TD)
* agoldvarg
* Ajitha Thayaharan (BCS Technology International)
* Alberto Arri (R3)
* amiracam
* Andras Slemmer (R3)
* Andrius Dagys (R3)
* Andrzej Cichocki (R3)
* Andrzej Grzesik (R3)
* Anthony Keenan (R3)
* Anthony Woolley (Société Générale)
* Anton Semenov (Commerzbank)
* Antonio Cerrato (SEB)
* Antony Lewis (R3)
* anttiai
* Arijit Das (Northern Trust)
* Arnaud Stevens (Natixis)
* Arun Battu (BNY Mellon)
* Austin Moothart (R3)
* Balaji Bhanudas More (Synechron)
* Barry Childe (HSBC)
* Barry Flower (Westpac)
* Bart van den Bosch (KBC)
* Ben Wyeth (RBS)
* Benjamin Abineri (R3)
* Benoit Lafontaine (OCTO)
* Berit Bourgonje (ING)
* BitcoinErrorLog
* BMO
* Bob Crozier (AIA)
* Bogdan Paunescu (R3)
* Bruno dos Santos Carvalhal (Ordina Nederland)
* C-Otto
* Cais Manai (R3)
* Carl Worrall (BCS)
* Carlos Kuchovsky (BBVA)
* Cédric Wahl (Société Générale)
* Chaitanya Jadhav (HSBC)
* chalkido
* Chris Akers (R3)
* Chris Burlinchon (R3)
* Chris Rankin (R3)
* Christian Kaufmann
* Christian Sailer (R3)
* Christopher Saunders
* Christopher Swanson (US Bank)
* Clark Thompson (R3)
* Clay Ratliff (Thoughtworks)
* Clemens Wan (R3)
* Clinton Alexander (R3)
* Clyde D'Cruz (Persistent Systems Limited)
* cncorda
* Credit Suisse
* cyrsis
* Dan Newton (Accenture)
* Daniel Roig (SEB)
* Dave Hudson (R3)
* David John Grundy (Dankse Bank)
* David Lee (BCS)
* Dinesh Rivankar (Persistent Systems Limited)
* Dirk Hermans (KBC)
* dmytrobr
* Edward Greenwood (State Street)
* Elendu Uche (APPZONE)
* Emanuel Russo (NTT DATA Italy)
* Farzad Pezeshkpour (RBS)
* fracting
* Frederic Dalibard (Natixis)
* Garrett Macey (Wells Fargo)
* gary-rowe
* Gavin Thomas (R3)
* George Marcel Smetana (Bradesco)
* Giulio Katis (Westpac)
* Giuseppe Cardone (Intesa Sanpaolo)
* Guy Hochstetler (R3)
* Ian Cusden (UBS)
* Ian Grigg (R3)
* Igor Nitto (R3)
* Igor Panov
* Ivan Schasny (R3)
* James Brown (R3)
* James Carlyle (R3)
* Jared Harwayne-Gidansky (BNY Mellon)
* Jayavaradhan Sambedu (Société Générale)
* Joel Dudley (R3)
* Johan Hörmark (SEB)
* Johann Palychata (BNP Paribas)
* johnnyychiu
* Jonathan Sartin (R3)
* Jonathan Sphar (R3)
* Jose Coll (R3)
* Jose Luu (Natixis)
* Josh Lindl (BCS)
* Justin Chapman (Northern Trust)
* Kai-Michael Schramm
* Karel Hajek (Barclays Capital)
* karnauskas
* Kasia Streich (R3)
* Kat Baker (R3)
* Keerthi Nelaturu (Scotiabank)
* Khaild Ahmed (Northern Trust)
* Klaus Apolinario (Bradesco)
* Koen Vingerhoets (KBC)
* Kostas Chalkias (R3)
* Lars Stage Thomsen (Danske Bank)
* Lee Braine (Barclays)
* Lucas Salmen (Itau)
* Lulu Ren (S-Labs)
* Maksymilian Pawlak (R3)
* Marek Scocovsky (ABSA)
* marekdapps
* Mark Lauer (Westpac)
* Mark Oldfield (R3)
* Mark Raynes (Thomson Reuters)
* Mark Simpson (RBS)
* Mark Tiggas (Wells Fargo)
* Massimo Morini
* Mat Rizzo (R3)
* Matt Britton (BCS)
* Matthew Layton (TradeIX)
* Matthew Nesbit (R3)
* Matthijs van den Bos (ING)
* Michal Kit (R3)
* Micheal Hinstridge (Thoughtworks)
* Michele Sollecito (R3)
* Mike Hearn (R3)
* Mike Ward (R3)
* Mike Reichelt (US Bank)
* Mohamed Amine LEGHERABA
* Mustafa Ozturk (Natixis)
* Nick Skinner (Northern Trust)
* Nigel King (R3)
* Nitesh Solanki (Persistent Systems Limited)
* Nuam Athaweth (MUFG)
* Oscar Zibordi de Paiva (Scopus Soluções em TI)
* OP Financial
* Patrick Kuo (R3)
* Pekka Kaipio (OP Financial)
* Phillip Griffin
* Piotr Piskorski (Nordea)
* Przemyslaw Bak (R3)
* quiark
* RangerOfFire
* Rex Maudsley (Société Générale)
* Rhett Brewer (Goldman Sachs)
* Richard Crook (RBS)
* Richard Gendal Brown (R3)
* Richard Green (R3)
* Richard Green (Blocksure)
* Rick Parker (R3)
* Roberto Karpinski (Bradesco)
* Robin Green (CIBC)
* Rodrigo Bueno (Itau)
* Rodrigo Gonçalves (Itau Unibanco)
* Roger Willis (R3)
* Ross Burnett (Macquarie)
* Ross Nicoll (R3)
* Rui Hu (Nordea)
* s-matthew-english
* sadysnaat
* Sajindra Jayasena (Deutsche Bank)
* Saket Sharma (BNY Mellon)
* Sam Chadwick (Thomson Reuters)
* Sasmit Sahu
* Scott James
* Sean Zhang (Wells Fargo)
* Shams Asari (R3)
* Siddhartha Sengupta (Tradewind Markets)
* Simon Taylor (Barclays)
* Sofus Mortensen (Digital Asset Holdings)
* stevenroose
* Stanly Johnson (Servntire Global)
* Szymon Sztuka (R3)
* tb-pq
* Thiago Rafael Ferreira (Scopus Soluções em TI)
* Thomas O'Donnell (Macquarie)
* Thomas Schroeter (R3)
* Tim Swanson (R3)
* Timothy Smith
* Tittu Varghese (Servntire Global)
* Tom Menner (R3)
* tomconte
* Tommy Lillehagen (R3)
* tomtau
* Tudor Malene (R3)
* Tushar Singh Bora (Accenture)
* varunkm
* Venelin Stoykov (INDUSTRIA)
* verymahler
* Viktor Kolomeyko (R3)
* Vipin Bharathan
* Wawrzek Niewodniczanski (R3)
* Wei Wu Zhang (Commonwealth Bank of Australia)
* Zabrina Smith (Northern Trust)

View File

@ -45,6 +45,7 @@ buildscript {
ext.guava_version = constants.getProperty("guavaVersion")
ext.caffeine_version = constants.getProperty("caffeineVersion")
ext.metrics_version = constants.getProperty("metricsVersion")
ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion")
ext.okhttp_version = '3.5.0'
ext.netty_version = '4.1.22.Final'
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")

View File

@ -8,7 +8,7 @@
# Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
#
gradlePluginsVersion=4.0.28
gradlePluginsVersion=4.0.29
kotlinVersion=1.2.51
platformVersion=4
guavaVersion=25.1-jre
@ -20,3 +20,4 @@ artifactoryPluginVersion=4.7.3
snakeYamlVersion=1.19
caffeineVersion=2.6.2
metricsVersion=3.2.5
metricsNewRelicVersion=1.1.1

View File

@ -27,9 +27,9 @@ sealed class NotaryError {
/** Specifies which states have already been consumed in another transaction. */
val consumedStates: Map<StateRef, StateConsumptionDetails>
) : NotaryError() {
override fun toString() = "Conflict notarising transaction $txId. " +
"Input states have been used in another transactions, count: ${consumedStates.size}, " +
"content: ${consumedStates.asSequence().joinToString(limit = 5) { it.key.toString() + "->" + it.value }}"
override fun toString() = "One or more input states have already been used in other transactions. Conflicting state count: ${consumedStates.size}, consumption details:\n" +
"${consumedStates.asSequence().joinToString(",\n", limit = 5) { it.key.toString() + " -> " + it.value }}.\n" +
"To find out if any of the conflicting transactions have been generated by this node you can use the hashLookup Corda shell command."
}
/** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */

View File

@ -108,7 +108,9 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
}
@Suspendable
private fun getInputTransactions(tx: SignedTransaction): Set<SecureHash> = tx.inputs.map { it.txhash }.toSet()
private fun getInputTransactions(tx: SignedTransaction): Set<SecureHash> {
return tx.inputs.map { it.txhash }.toSet() + tx.references.map { it.txhash }.toSet()
}
private class TransactionAuthorisationFilter(private val authorisedTransactions: MutableSet<SecureHash> = mutableSetOf(), val acceptAll: Boolean = false) {
fun isAuthorised(txId: SecureHash) = acceptAll || authorisedTransactions.contains(txId)

View File

@ -22,7 +22,7 @@ import net.corda.core.utilities.unwrap
abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic<Void?>() {
companion object {
// TODO: Determine an appropriate limit and also enforce in the network parameters and the transaction builder.
private const val maxAllowedInputs = 10_000
private const val maxAllowedInputsAndReferences = 10_000
}
@Suspendable
@ -51,9 +51,10 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
/** Checks whether the number of input states is too large. */
protected fun checkInputs(inputs: List<StateRef>) {
if (inputs.size > maxAllowedInputs) {
if (inputs.size > maxAllowedInputsAndReferences) {
val error = NotaryError.TransactionInvalid(
IllegalArgumentException("A transaction cannot have more than $maxAllowedInputs inputs, received: ${inputs.size}")
IllegalArgumentException("A transaction cannot have more than $maxAllowedInputsAndReferences " +
"inputs or references, received: ${inputs.size}")
)
throw NotaryInternalException(error)
}

View File

@ -26,7 +26,14 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
* this method does not throw an exception when input states are present multiple times within the transaction.
*/
@JvmOverloads
fun commitInputStates(inputs: List<StateRef>, txId: SecureHash, caller: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef> = emptyList()) {
fun commitInputStates(
inputs: List<StateRef>,
txId: SecureHash,
caller: Party,
requestSignature: NotarisationRequestSignature,
timeWindow: TimeWindow?,
references: List<StateRef> = emptyList()
) {
try {
uniquenessProvider.commit(inputs, txId, caller, requestSignature, timeWindow, references)
} catch (e: NotaryInternalException) {

View File

@ -140,7 +140,7 @@ Signing the transaction
Now that we have a valid transaction proposal, we need to sign it. Once the transaction is signed, no-one will be able
to modify the transaction without invalidating this signature. This effectively makes the transaction immutable.
We sign the transaction using ``ServiceHub.toSignedTransaction``, which returns a ``SignedTransaction``. A
We sign the transaction using ``ServiceHub.signInitialTransaction``, which returns a ``SignedTransaction``. A
``SignedTransaction`` is an object that pairs a transaction with a list of signatures over that transaction.
Finalising the transaction

View File

@ -109,10 +109,6 @@ We want to create an IOU of 99 with PartyB. We start the ``IOUFlow`` by typing:
.. container:: codeset
.. code-block:: java
start IOUFlow arg0: 99, arg1: "O=PartyB,L=New York,C=US"
.. code-block:: kotlin
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"

View File

@ -17,7 +17,7 @@ CorDapp onto a local test network of dummy nodes to test its functionality.
CorDapps can be written in both Java and Kotlin, and will be providing the code in both languages in this tutorial.
Note that there's no need to download and install Corda itself. Corda V1.0's required libraries will be downloaded
Note that there's no need to download and install Corda itself. Corda's required libraries will be downloaded
automatically from an online Maven repository.
Downloading the template

View File

@ -286,12 +286,12 @@ Each node webserver exposes the following endpoints:
There is also a web front-end served from ``/web/example``.
.. warning:: The content in ``web/example`` is only available for demonstration purposes and does not implement
.. warning:: The content in ``/web/example`` is only available for demonstration purposes and does not implement
anti-XSS, anti-XSRF or other security techniques. Do not use this code in production.
Creating an IOU via the endpoint
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An IOU can be created by sending a PUT request to the ``api/example/create-iou`` endpoint directly, or by using the
An IOU can be created by sending a PUT request to the ``/api/example/create-iou`` endpoint directly, or by using the
the web form served from ``/web/example``.
To create an IOU between PartyA and PartyB, run the following command from the command line:

View File

@ -297,21 +297,22 @@ s participants agree to the proposed upgrade. The following combinations of upgr
* A state is upgraded while the contract stays the same.
* The state and the contract are updated simultaneously.
The procedure for updating a state or a contract using a flag-day approach is quite simple:
Performing explicit contract and state upgrades
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Update and test the state or contract.
* Produce a new CorDapp JAR file and distribute it to all the relevant parties.
* Each node operator stops their node, replaces the existing JAR with the new one, and restarts. They may wish to do
a node drain first to avoid the definition of states or contracts changing whilst a flow is in progress.
* Run the contract upgrade authorisation flow for each state that requires updating on every node.
* For each state, one node should run the contract upgrade initiation flow, which will contact the rest.
1. Preserve the existing state and contract definitions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Currently, all nodes must **permanently** keep **all** old state and contract definitions on their node's classpath
Update Process
~~~~~~~~~~~~~~
.. note:: Once the contract-code-as-an-attachment feature has been implemented, nodes will only be required to keep the
old state and contract definitions on their node's classpath for the duration of the upgrade
Writing the new state and contract definitions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Start by updating the contract and/or state definitions. There are no restrictions on how states are updated. However,
Changing a state or contract's package constitutes a definition change. If you want to move a state or contract
definition to a new package, you must also preserve the definition in the old package
2. Write the new state and contract definitions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Update the contract and/or state definitions. There are no restrictions on how states are updated. However,
upgraded contracts must implement the ``UpgradedContract`` interface. This interface is defined as:
.. sourcecode:: kotlin
@ -341,16 +342,40 @@ For example, in case of hash constraints the hash of the legacy JAR file should
override val legacyContractConstraint: AttachmentConstraint
get() = HashAttachmentConstraint(SecureHash.parse("E02BD2B9B010BBCE49C0D7C35BECEF2C79BEB2EE80D902B54CC9231418A4FA0C"))
Authorising the upgrade
^^^^^^^^^^^^^^^^^^^^^^^
Once the new states and contracts are on the classpath for all the relevant nodes, the next step is for all nodes to
run the ``ContractUpgradeFlow.Authorise`` flow. This flow takes a ``StateAndRef`` of the state to update as well as a
reference to the new contract, which must implement the ``UpgradedContract`` interface.
3. Create the new CorDapp JAR
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Produce a new CorDapp JAR file. This JAR file should only contain the new contract and state definitions.
4. Distribute the new CorDapp JAR
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Place the new CorDapp JAR file in the ``cordapps`` folder of all the relevant nodes. You can do this while the nodes are still
running.
5. Stop the nodes
^^^^^^^^^^^^^^^^^
Have each node operator stop their node. If you are also changing flow definitions, you should perform a
:ref:`node drain <draining_the_node>` first to avoid the definition of states or contracts changing whilst a flow is
in progress.
6. Re-run the network bootstrapper
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you're using the network bootstrapper instead of a network map server and have defined any new contracts, you need to
re-run the network bootstrapper to whitelist the new contracts. See :doc:`network-bootstrapper`.
7. Restart the nodes
^^^^^^^^^^^^^^^^^^^^
Have each node operator restart their node.
8. Authorise the upgrade
^^^^^^^^^^^^^^^^^^^^^^^^
Now that new states and contracts are on the classpath for all the relevant nodes, the next step is for all node to run the
``ContractUpgradeFlow.Authorise`` flow. This flow takes a ``StateAndRef`` of the state to update as well as a reference
to the new contract, which must implement the ``UpgradedContract`` interface.
At any point, a node administrator may de-authorise a contract upgrade by running the
``ContractUpgradeFlow.Deauthorise`` flow.
Performing the upgrade
9. Perform the upgrade
^^^^^^^^^^^^^^^^^^^^^^
Once all nodes have performed the authorisation process, a participant must be chosen to initiate the upgrade via the
``ContractUpgradeFlow.Initiate`` flow for each state object. This flow has the following signature:

View File

@ -34,7 +34,7 @@ object SampleCashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, ve
override val migrationResource = "sample-cash-v1.changelog-init"
@Entity
@Table(name = "contract_cash_states_v1", indexes = [Index(name = "ccy_code_idx", columnList = "ccy_code"), Index(name = "pennies_idx", columnList = "pennies")])
@Table(name = "contract_cash_states_v1", indexes = [Index(name = "ccy_code_idx1", columnList = "ccy_code"), Index(name = "pennies_idx1", columnList = "pennies")])
class PersistentCashState(
@Column(name = "owner_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var ownerHash: String,

View File

@ -238,7 +238,7 @@ dependencies {
// Jolokia JVM monitoring agent, required to push logs through slf4j
compile "org.jolokia:jolokia-jvm:${jolokia_version}:agent"
// Optional New Relic JVM reporter, used to push metrics to the configured account associated with a newrelic.yml configuration. See https://mvnrepository.com/artifact/com.palominolabs.metrics/metrics-new-relic
compile group: 'com.palominolabs.metrics', name: 'metrics-new-relic', version: '1.1.1'
compile "com.palominolabs.metrics:metrics-new-relic:${metrics_new_relic_version}"
// Allow access to simple SOCKS Server for integration testing
testCompile("io.netty:netty-example:$netty_version") {

View File

@ -56,6 +56,8 @@ import java.lang.management.ManagementFactory
import java.net.InetAddress
import java.nio.file.Path
import java.nio.file.Paths
import java.time.DayOfWeek
import java.time.ZonedDateTime
import java.util.*
import kotlin.system.exitProcess
@ -539,8 +541,8 @@ open class NodeStartup(val args: Array<String>) {
"It's kind of like a block chain but\ncords sounded healthier than chains.",
"Computer science and finance together.\nYou should see our crazy Christmas parties!",
"I met my bank manager yesterday and asked\nto check my balance ... he pushed me over!",
"A banker with nobody around may find\nthemselves .... a-loan! <applause>",
"Whenever I go near my bank I get\nwithdrawal symptoms ${Emoji.coolGuy}",
"A banker left to their own devices may find\nthemselves .... a-loan! <applause>",
"Whenever I go near my bank\nI get withdrawal symptoms ${Emoji.coolGuy}",
"There was an earthquake in California,\na local bank went into de-fault.",
"I asked for insurance if the nearby\nvolcano erupted. They said I'd be covered.",
"I had an account with a bank in the\nNorth Pole, but they froze all my assets ${Emoji.santaClaus}",
@ -551,11 +553,48 @@ open class NodeStartup(val args: Array<String>) {
"I won $3M on the lottery so I donated a quarter\nof it to charity. Now I have $2,999,999.75.",
"There are two rules for financial success:\n1) Don't tell everything you know.",
"Top tip: never say \"oops\", instead\nalways say \"Ah, Interesting!\"",
"Computers are useless. They can only\ngive you answers. -- Picasso"
"Computers are useless. They can only\ngive you answers. -- Picasso",
"Regular naps prevent old age, especially\nif you take them whilst driving.",
"Always borrow money from a pessimist.\nHe won't expect it back.",
"War does not determine who is right.\nIt determines who is left.",
"A bus stops at a bus station. A train stops at a\ntrain station. What happens at a workstation?",
"I got a universal remote control yesterday.\nI thought, this changes everything.",
"Did you ever walk into an office and\nthink, whiteboards are remarkable!",
"The good thing about lending out your time machine\nis that you basically get it back immediately.",
"I used to work in a shoe recycling\nshop. It was sole destroying.",
"What did the fish say\nwhen he hit a wall? Dam.",
"You should really try a seafood diet.\nIt's easy: you see food and eat it.",
"I recently sold my vacuum cleaner,\nall it was doing was gathering dust.",
"My professor accused me of plagiarism.\nHis words, not mine!",
"Change is inevitable, except\nfrom a vending machine.",
"If at first you don't succeed, destroy\nall the evidence that you tried.",
"If at first you don't succeed, \nthen we have something in common!",
"Moses had the first tablet that\ncould connect to the cloud.",
"How did my parents fight boredom before the internet?\nI asked my 17 siblings and they didn't know either.",
"Cats spend two thirds of their lives sleeping\nand the other third making viral videos.",
"The problem with troubleshooting\nis that trouble shoots back.",
"I named my dog 'Six Miles' so I can tell\npeople I walk Six Miles every day.",
"People used to laugh at me when I said I wanted\nto be a comedian. Well they're not laughing now!",
"My wife just found out I replaced our bed\nwith a trampoline; she hit the roof.",
"My boss asked me who is the stupid one, me or him?\nI said everyone knows he doesn't hire stupid people.",
"Don't trust atoms.\nThey make up everything.",
"Keep the dream alive:\nhit the snooze button.",
"Rest in peace, boiled water.\nYou will be mist.",
"When I discovered my toaster wasn't\nwaterproof, I was shocked.",
"Where do cryptographers go for\nentertainment? The security theatre.",
"How did the Java programmer get rich?\nThey inherited a factory.",
"Why did the developer quit his job?\nHe didn't get ar-rays."
)
if (Emoji.hasEmojiTerminal)
messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}"
if (ZonedDateTime.now().dayOfWeek == DayOfWeek.FRIDAY) {
// Make it quite likely people see it.
repeat(20) { messages += "Ah, Friday.\nMy second favourite F-word." }
}
val (msg1, msg2) = messages.randomOrNull()!!.split('\n')
println(Ansi.ansi().newline().fgBrightRed().a(
@ -563,7 +602,8 @@ open class NodeStartup(val args: Array<String>) {
""" / ____/ _________/ /___ _""").newline().a(
""" / / __ / ___/ __ / __ `/ """).fgBrightBlue().a(msg1).newline().fgBrightRed().a(
"""/ /___ /_/ / / / /_/ / /_/ / """).fgBrightBlue().a(msg2).newline().fgBrightRed().a(
"""\____/ /_/ \__,_/\__,_/""").reset().newline().newline().fgBrightDefault().bold().a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------").newline().newline().reset())
"""\____/ /_/ \__,_/\__,_/""").reset().newline().newline().fgBrightDefault().bold().a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -------------------------------------------------------------").newline().newline().reset())
}
}
}

View File

@ -75,43 +75,41 @@ interface ServiceHubInternal : ServiceHub {
log.warn("Transactions recorded from outside of a state machine")
}
if (statesToRecord != StatesToRecord.NONE) {
// When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
// that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
// is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
//
// The reason for this is three-fold:
//
// 1) We are putting in place the observer mode feature relatively quickly to meet specific customer
// launch target dates.
//
// 2) The right design for vaults which mix observations and relevant states isn't entirely clear yet.
//
// 3) If we get the design wrong it could create security problems and business confusions.
//
// Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the
// Bitcoin equivalent of observer nodes:
//
// https://bitcoinj.github.io/working-with-the-wallet#watching-wallets
//
// The ability to have a wallet containing both irrelevant and relevant states complicated everything quite
// dramatically, even methods as basic as the getBalance() API which required additional modes to let you
// query "balance I can spend" vs "balance I am observing". In the end it might have been better to just
// require the user to create an entirely separate wallet for observing with.
//
// In Corda we don't support a single node having multiple vaults (at the time of writing), and it's not
// clear that's the right way to go: perhaps adding an "origin" column to the VAULT_STATES table is a better
// solution. Then you could select subsets of states depending on where the report came from.
//
// The risk of doing this is that apps/developers may use 'canned SQL queries' not written by us that forget
// to add a WHERE clause for the origin column. Those queries will seem to work most of the time until
// they're run on an observer node and mix in irrelevant data. In the worst case this may result in
// erroneous data being reported to the user, which could cause security problems.
//
// Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
// to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
vaultService.notifyAll(statesToRecord, recordedTransactions.map { it.coreTransaction })
}
// When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
// that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
// is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
//
// The reason for this is three-fold:
//
// 1) We are putting in place the observer mode feature relatively quickly to meet specific customer
// launch target dates.
//
// 2) The right design for vaults which mix observations and relevant states isn't entirely clear yet.
//
// 3) If we get the design wrong it could create security problems and business confusions.
//
// Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the
// Bitcoin equivalent of observer nodes:
//
// https://bitcoinj.github.io/working-with-the-wallet#watching-wallets
//
// The ability to have a wallet containing both irrelevant and relevant states complicated everything quite
// dramatically, even methods as basic as the getBalance() API which required additional modes to let you
// query "balance I can spend" vs "balance I am observing". In the end it might have been better to just
// require the user to create an entirely separate wallet for observing with.
//
// In Corda we don't support a single node having multiple vaults (at the time of writing), and it's not
// clear that's the right way to go: perhaps adding an "origin" column to the VAULT_STATES table is a better
// solution. Then you could select subsets of states depending on where the report came from.
//
// The risk of doing this is that apps/developers may use 'canned SQL queries' not written by us that forget
// to add a WHERE clause for the origin column. Those queries will seem to work most of the time until
// they're run on an observer node and mix in irrelevant data. In the worst case this may result in
// erroneous data being reported to the user, which could cause security problems.
//
// Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
// to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
vaultService.notifyAll(statesToRecord, recordedTransactions.map { it.coreTransaction })
}
}
}

View File

@ -202,8 +202,8 @@ class StaffedFlowHospital {
return Diagnosis.DISCHARGE
} else {
val errorMsg = "Maximum number of retries reached for flow ${flowFiber.snapshot().flowLogic.javaClass}. " +
"If the flow involves notarising a transaction, this usually means that the notary is being overloaded and " +
"unable to service requests fast enough. Please try again later."
"If the flow involves notarising a transaction, it means that no response was received from the notary." +
"This could be either due to the the notary being overloaded or unable to reach this node."
newError.setMessage(errorMsg)
log.warn(errorMsg)
}

View File

@ -149,10 +149,11 @@ class BFTNonValidatingNotaryService(
return try {
val id = transaction.id
val inputs = transaction.inputs
val references = transaction.references
val notary = transaction.notary
val timeWindow = (transaction as? FilteredTransaction)?.timeWindow
if (notary !in services.myInfo.legalIdentities) throw NotaryInternalException(NotaryError.WrongNotary)
commitInputStates(inputs, id, callerIdentity.name, requestSignature, timeWindow)
commitInputStates(inputs, id, callerIdentity.name, requestSignature, timeWindow, references)
log.debug { "Inputs committed successfully, signing $id" }
BFTSMaRt.ReplicaResponse.Signature(sign(id))
} catch (e: NotaryInternalException) {

View File

@ -233,17 +233,35 @@ object BFTSMaRt {
*/
abstract fun executeCommand(command: ByteArray): ByteArray?
protected fun commitInputStates(states: List<StateRef>, txId: SecureHash, callerName: CordaX500Name, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?) {
private fun checkConflict(
conflictingStates: LinkedHashMap<StateRef, StateConsumptionDetails>,
states: List<StateRef>,
type: StateConsumptionDetails.ConsumedStateType
) {
states.forEach { stateRef ->
commitLog[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.sha256(), type) }
}
}
protected fun commitInputStates(
states: List<StateRef>,
txId: SecureHash,
callerName: CordaX500Name,
requestSignature: NotarisationRequestSignature,
timeWindow: TimeWindow?,
references: List<StateRef> = emptyList()
) {
log.debug { "Attempting to commit inputs for transaction: $txId" }
services.database.transaction {
logRequest(txId, callerName, requestSignature)
val conflictingStates = LinkedHashMap<StateRef, StateConsumptionDetails>()
for (state in states) {
commitLog[state]?.let { conflictingStates[state] = StateConsumptionDetails(it.sha256()) }
}
checkConflict(conflictingStates, states, StateConsumptionDetails.ConsumedStateType.INPUT_STATE)
checkConflict(conflictingStates, references, StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE)
if (conflictingStates.isNotEmpty()) {
if (!isConsumedByTheSameTx(txId.sha256(), conflictingStates)) {
log.debug { "Failure, input states already committed: ${conflictingStates.keys}" }
log.debug { "Failure, input states or references already committed: ${conflictingStates.keys}" }
throw NotaryInternalException(NotaryError.Conflict(txId, conflictingStates))
}
} else {

View File

@ -34,7 +34,7 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAut
@Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
val transaction = requestPayload.coreTransaction
checkInputs(transaction.inputs)
checkInputs(transaction.inputs + transaction.references)
val request = NotarisationRequest(transaction.inputs, transaction.id)
validateRequestSignature(request, requestPayload.requestSignature)
val parts = extractParts(transaction)

View File

@ -41,7 +41,7 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthor
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
try {
val stx = requestPayload.signedTransaction
checkInputs(stx.inputs)
checkInputs(stx.inputs + stx.references)
validateRequestSignature(NotarisationRequest(stx.inputs, stx.id), requestPayload.requestSignature)
val notary = stx.notary
checkNotary(notary)

View File

@ -43,7 +43,9 @@ abstract class AbstractQueryCriteriaParser<Q : GenericQueryCriteria<Q,P>, in P:
val leftPredicates = parse(left)
val rightPredicates = parse(right)
val orPredicate = criteriaBuilder.or(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())
val leftAnd = criteriaBuilder.and(*leftPredicates.toTypedArray())
val rightAnd = criteriaBuilder.and(*rightPredicates.toTypedArray())
val orPredicate = criteriaBuilder.or(leftAnd,rightAnd)
predicateSet.add(orPredicate)
return predicateSet
@ -183,8 +185,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
private val log = contextLogger()
}
// incrementally build list of join predicates
private val joinPredicates = mutableListOf<Predicate>()
// incrementally build list of root entities (for later use in Sort parsing)
private val rootEntities = mutableMapOf<Class<out PersistentState>, Root<*>>(Pair(VaultSchemaV1.VaultStates::class.java, vaultStates))
private val aggregateExpressions = mutableListOf<Expression<*>>()
@ -340,8 +340,14 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
val predicateSet = mutableSetOf<Predicate>()
val vaultFungibleStates = criteriaQuery.from(VaultSchemaV1.VaultFungibleStates::class.java)
rootEntities.putIfAbsent(VaultSchemaV1.VaultFungibleStates::class.java, vaultFungibleStates)
// ensure we re-use any existing instance of the same root entity
val entityStateClass = VaultSchemaV1.VaultFungibleStates::class.java
val vaultFungibleStates =
rootEntities.getOrElse(entityStateClass) {
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
entityRoot
}
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultFungibleStates.get<PersistentStateRef>("stateRef"))
predicateSet.add(joinPredicate)
@ -372,7 +378,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
// participants
criteria.participants?.let {
val participants = criteria.participants as List<AbstractParty>
val joinLinearStateToParty = vaultFungibleStates.joinSet<VaultSchemaV1.VaultLinearStates, AbstractParty>("participants")
val joinLinearStateToParty = vaultFungibleStates.joinSet<VaultSchemaV1.VaultFungibleStates, AbstractParty>("participants")
predicateSet.add(criteriaBuilder.and(joinLinearStateToParty.`in`(participants)))
criteriaQuery.distinct(true)
}
@ -384,11 +390,17 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
val predicateSet = mutableSetOf<Predicate>()
val vaultLinearStates = criteriaQuery.from(VaultSchemaV1.VaultLinearStates::class.java)
rootEntities.putIfAbsent(VaultSchemaV1.VaultLinearStates::class.java, vaultLinearStates)
// ensure we re-use any existing instance of the same root entity
val entityStateClass = VaultSchemaV1.VaultLinearStates::class.java
val vaultLinearStates =
rootEntities.getOrElse(entityStateClass) {
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
entityRoot
}
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultLinearStates.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate)
predicateSet.add(joinPredicate)
// linear ids UUID
criteria.uuid?.let {
@ -417,22 +429,28 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
log.trace { "Parsing VaultCustomQueryCriteria: $criteria" }
val predicateSet = mutableSetOf<Predicate>()
val entityClass = resolveEnclosingObjectFromExpression(criteria.expression)
val entityStateClass = resolveEnclosingObjectFromExpression(criteria.expression)
try {
val entityRoot = criteriaQuery.from(entityClass)
rootEntities.putIfAbsent(entityClass, entityRoot)
// ensure we re-use any existing instance of the same root entity
val entityRoot =
rootEntities.getOrElse(entityStateClass) {
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
entityRoot
}
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), entityRoot.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate)
predicateSet.add(joinPredicate)
// resolve general criteria expressions
parseExpression(entityRoot, criteria.expression, predicateSet)
@Suppress("UNCHECKED_CAST")
parseExpression(entityRoot as Root<L>, criteria.expression, predicateSet)
} catch (e: Exception) {
e.message?.let { message ->
if (message.contains("Not an entity"))
throw VaultQueryException("""
Please register the entity '${entityClass.name}'
Please register the entity '${entityStateClass.name}'
See https://docs.corda.net/api-persistence.html#custom-schema-registration for more information""")
}
throw VaultQueryException("Parsing error: ${e.message}")
@ -454,7 +472,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
else
aggregateExpressions
criteriaQuery.multiselect(selections)
val combinedPredicates = joinPredicates.plus(predicateSet).plus(commonPredicates.values)
val combinedPredicates = commonPredicates.values.plus(predicateSet)
criteriaQuery.where(*combinedPredicates.toTypedArray())
return predicateSet
@ -512,8 +530,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
// scenario where sorting on attributes not parsed as criteria
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), entityRoot.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate)
entityRoot
}
when (direction) {
@ -532,7 +548,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
}
if (orderCriteria.isNotEmpty()) {
criteriaQuery.orderBy(orderCriteria)
criteriaQuery.where(*joinPredicates.toTypedArray())
}
}

View File

@ -520,9 +520,9 @@ class NodeVaultService(
val results = query.resultList
// final pagination check (fail-fast on too many results when no pagination specified)
if (!skipPagingChecks && paging.isDefault && results.size > DEFAULT_PAGE_SIZE)
throw VaultQueryException("Please specify a `PageSpecification` as there are more results [${results.size}] than the default page size [$DEFAULT_PAGE_SIZE]")
if (!skipPagingChecks && paging.isDefault && results.size > DEFAULT_PAGE_SIZE) {
throw VaultQueryException("There are ${results.size} results, which exceeds the limit of $DEFAULT_PAGE_SIZE for queries that do not specify paging. In order to retrieve these results, provide a `PageSpecification(pageNumber, pageSize)` to the method invoked.")
}
val statesAndRefs: MutableList<StateAndRef<T>> = mutableListOf()
val statesMeta: MutableList<Vault.StateMetadata> = mutableListOf()
val otherResults: MutableList<Any> = mutableListOf()

View File

@ -52,6 +52,7 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static net.corda.core.node.services.vault.Builder.sum;
import static net.corda.core.node.services.vault.QueryCriteriaUtils.*;
@ -326,13 +327,13 @@ public class VaultQueryJavaTests {
@Test
public void trackDealStatesPagedSorted() {
List<String> dealIds = asList("123", "456", "789");
UniqueIdentifier uid =
database.transaction(tx -> {
Vault<LinearState> states = vaultFiller.fillWithSomeTestLinearStates(10, null);
UniqueIdentifier _uid = states.getStates().iterator().next().component1().getData().getLinearId();
vaultFiller.fillWithSomeTestDeals(dealIds);
return _uid;
});
UniqueIdentifier uid = new UniqueIdentifier("999");
database.transaction(tx -> {
vaultFiller.fillWithSomeTestLinearStates(10, null);
vaultFiller.fillWithSomeTestLinearStates(1, null, emptyList(), uid);
vaultFiller.fillWithSomeTestDeals(dealIds);
return tx;
});
database.transaction(tx -> {
// DOCSTART VaultJavaQueryExample5
@SuppressWarnings("unchecked")
@ -354,7 +355,7 @@ public class VaultQueryJavaTests {
Vault.Page<ContractState> snapshot = results.getSnapshot();
// DOCEND VaultJavaQueryExample5
assertThat(snapshot.getStates()).hasSize(13);
assertThat(snapshot.getStates()).hasSize(4);
return tx;
});

View File

@ -14,6 +14,7 @@ import com.nhaarman.mockito_kotlin.*
import net.corda.core.contracts.Amount
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty
@ -631,6 +632,52 @@ class HibernateConfigurationTest {
assertThat(queryResults).hasSize(10)
}
/**
* Composite OR query
*/
@Test
fun `composite or query across VaultStates, VaultLinearStates and DummyLinearStates`() {
val uniqueID456 = UniqueIdentifier("456")
database.transaction {
vaultFiller.fillWithSomeTestLinearStates(1, externalId = "123", linearString = "123", linearNumber = 123, linearBoolean = true)
vaultFiller.fillWithSomeTestLinearStates(1, uniqueIdentifier = uniqueID456)
vaultFiller.fillWithSomeTestLinearStates(1, externalId = "789")
}
val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV1)
val criteriaBuilder = sessionFactory.criteriaBuilder
val entityManager = sessionFactory.createEntityManager()
// structure query
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultStates::class.java)
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
val vaultLinearStates = criteriaQuery.from(VaultSchemaV1.VaultLinearStates::class.java)
val dummyLinearStates = criteriaQuery.from(DummyLinearStateSchemaV1.PersistentDummyLinearState::class.java)
criteriaQuery.select(vaultStates)
val joinPredicate1 = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultLinearStates.get<PersistentStateRef>("stateRef"))
val joinPredicate2 = criteriaBuilder.and(criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), dummyLinearStates.get<PersistentStateRef>("stateRef")))
// and predicates on VaultLinearStates
val andLinearStatesPredicate1 = criteriaBuilder.and(criteriaBuilder.equal(vaultLinearStates.get<String>("externalId"), uniqueID456.externalId))
val andLinearStatesPredicate2 = criteriaBuilder.and(criteriaBuilder.equal(vaultLinearStates.get<UUID>("uuid"), uniqueID456.id))
val andLinearStatesPredicate = criteriaBuilder.and(andLinearStatesPredicate1, andLinearStatesPredicate2)
// and predicates on PersistentDummyLinearState
val andDummyLinearStatesPredicate1 = criteriaBuilder.and(criteriaBuilder.equal(dummyLinearStates.get<String>("linearString"), "123"))
val andDummyLinearStatesPredicate2 = criteriaBuilder.and(criteriaBuilder.equal(dummyLinearStates.get<Long>("linearNumber"), 123L))
val andDummyLinearStatesPredicate3 = criteriaBuilder.and(criteriaBuilder.equal(dummyLinearStates.get<Boolean>("linearBoolean"), true))
val andDummyLinearStatesPredicate = criteriaBuilder.and(andDummyLinearStatesPredicate1, criteriaBuilder.and(andDummyLinearStatesPredicate2, andDummyLinearStatesPredicate3))
// or predicates
val orPredicates = criteriaBuilder.or(andLinearStatesPredicate, andDummyLinearStatesPredicate)
criteriaQuery.where(joinPredicate1, joinPredicate2, orPredicates)
// execute query
val queryResults = entityManager.createQuery(criteriaQuery).resultList
assertThat(queryResults).hasSize(2)
}
/**
* Test a OneToOne table mapping
*/

View File

@ -1186,7 +1186,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
@Test
fun `pagination not specified but more than default results available`() {
expectedEx.expect(VaultQueryException::class.java)
expectedEx.expectMessage("Please specify a `PageSpecification`")
expectedEx.expectMessage("provide a `PageSpecification(pageNumber, pageSize)`")
database.transaction {
vaultFiller.fillWithSomeTestCash(201.DOLLARS, notaryServices, 201, DUMMY_CASH_ISSUER)
@ -1460,11 +1460,12 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
@Test
fun `unconsumed deal states sorted`() {
database.transaction {
val linearStates = vaultFiller.fillWithSomeTestLinearStates(10)
vaultFiller.fillWithSomeTestLinearStates(10)
val uid = UniqueIdentifier("999")
vaultFiller.fillWithSomeTestLinearStates(1, uniqueIdentifier = uid)
vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789"))
val uid = linearStates.states.first().state.data.linearId.id
val linearStateCriteria = LinearStateQueryCriteria(uuid = listOf(uid))
val linearStateCriteria = LinearStateQueryCriteria(uuid = listOf(uid.id))
val dealStateCriteria = LinearStateQueryCriteria(externalId = listOf("123", "456", "789"))
val compositeCriteria = linearStateCriteria or dealStateCriteria
@ -1972,6 +1973,63 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
}
}
@Test
fun `composite query for fungible and linear states`() {
database.transaction {
vaultFiller.fillWithSomeTestLinearStates(1, "TEST1")
vaultFiller.fillWithSomeTestDeals(listOf("123"))
vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 1, DUMMY_CASH_ISSUER, services.myInfo.singleIdentity())
vaultFiller.fillWithSomeTestCommodity(Amount(100, Commodity.getInstance("FCOJ")!!), notaryServices, DUMMY_OBLIGATION_ISSUER.ref(1))
vaultFiller.fillWithDummyState()
// all contract states query
val results = vaultService.queryBy<ContractState>()
assertThat(results.states).hasSize(5)
// linear states only query
val linearStateCriteria = LinearStateQueryCriteria()
val resultsLSC = vaultService.queryBy<ContractState>(linearStateCriteria)
assertThat(resultsLSC.states).hasSize(2)
// fungible asset states only query
val fungibleAssetStateCriteria = FungibleAssetQueryCriteria()
val resultsFASC = vaultService.queryBy<ContractState>(fungibleAssetStateCriteria)
assertThat(resultsFASC.states).hasSize(2)
// composite OR query for both linear and fungible asset states (eg. all states in either Fungible and Linear states tables)
val resultsCompositeOr = vaultService.queryBy<ContractState>(fungibleAssetStateCriteria.or(linearStateCriteria))
assertThat(resultsCompositeOr.states).hasSize(4)
// composite AND query for both linear and fungible asset states (eg. all states in both Fungible and Linear states tables)
val resultsCompositeAnd = vaultService.queryBy<ContractState>(fungibleAssetStateCriteria.and(linearStateCriteria))
assertThat(resultsCompositeAnd.states).hasSize(0)
}
}
@Test
fun `composite query for fungible and linear states for multiple participants`() {
database.transaction {
identitySvc.verifyAndRegisterIdentity(ALICE_IDENTITY)
identitySvc.verifyAndRegisterIdentity(BOB_IDENTITY)
identitySvc.verifyAndRegisterIdentity(CHARLIE_IDENTITY)
vaultFiller.fillWithSomeTestLinearStates(1, "TEST1", listOf(ALICE))
vaultFiller.fillWithSomeTestLinearStates(1, "TEST2", listOf(BOB))
vaultFiller.fillWithSomeTestLinearStates(1, "TEST3", listOf(CHARLIE))
vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 1, DUMMY_CASH_ISSUER)
vaultFiller.fillWithSomeTestCommodity(Amount(100, Commodity.getInstance("FCOJ")!!), notaryServices, DUMMY_OBLIGATION_ISSUER.ref(1))
vaultFiller.fillWithDummyState()
// all contract states query
val results = vaultService.queryBy<ContractState>()
assertThat(results.states).hasSize(6)
// linear states by participants only query
val linearStateCriteria = LinearStateQueryCriteria(participants = listOf(ALICE,BOB))
val resultsLSC = vaultService.queryBy<ContractState>(linearStateCriteria)
assertThat(resultsLSC.states).hasSize(2)
// fungible asset states by participants only query
val fungibleAssetStateCriteria = FungibleAssetQueryCriteria(participants = listOf(services.myInfo.singleIdentity()))
val resultsFASC = vaultService.queryBy<ContractState>(fungibleAssetStateCriteria)
assertThat(resultsFASC.states).hasSize(2)
// composite query for both linear and fungible asset states by participants
val resultsComposite = vaultService.queryBy<ContractState>(linearStateCriteria.or(fungibleAssetStateCriteria))
assertThat(resultsComposite.states).hasSize(4)
}
}
@Test
fun `unconsumed linear heads where external id is null`() {
database.transaction {

View File

@ -16,14 +16,17 @@ apply plugin: 'net.corda.plugins.cordformation'
dependencies {
cordaCompile project(":core")
cordaCompile project(":node-api")
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaRuntime project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
ext.rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
directory "./build/nodes"
directory file("$buildDir/nodes")
nodeDefaults {
cordapps = []
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
}
node {
name "O=Notary Service,L=Zurich,C=CH"
notary = [validating : true]
@ -32,13 +35,12 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
port 10003
adminPort 10004
}
rpcUsers = []
extraConfig = ['h2Settings.address' : 'localhost:10005']
}
node {
name "O=Bank A,L=London,C=GB"
p2pPort 10006
cordapps = []
rpcUsers = ext.rpcUsers
// This configures the default cordapp for this node
projectCordapp {
config "someStringValue=test"
@ -52,8 +54,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
node {
name "O=Bank B,L=New York,C=US"
p2pPort 10010
cordapps = []
rpcUsers = ext.rpcUsers
// This configures the default cordapp for this node
projectCordapp {
config project.file("src/config.conf")

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>

View File

@ -58,8 +58,8 @@ dependencies {
cordapp project(':samples:simm-valuation-demo:flows')
// Corda integration dependencies
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaRuntime project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
cordaCompile project(':core')
cordaCompile project(':webserver')
@ -87,20 +87,22 @@ dependencies {
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
ext.rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
directory "./build/nodes"
directory file("$buildDir/nodes")
nodeDefaults {
cordapp project(':finance')
cordapp project(':samples:simm-valuation-demo:contracts-states')
cordapp project(':samples:simm-valuation-demo:flows')
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
}
node {
name "O=Notary Service,L=Zurich,C=CH"
notary = [validating : true]
p2pPort 10002
cordapp project(':finance')
cordapp project(':samples:simm-valuation-demo:contracts-states')
cordapp project(':samples:simm-valuation-demo:flows')
rpcSettings {
address "localhost:10014"
adminAddress "localhost:10015"
}
rpcUsers = []
extraConfig = [
custom: [
jvmArgs: ["-Xmx1g"]
@ -116,10 +118,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
address("localhost:10016")
adminAddress("localhost:10017")
}
cordapp project(':finance')
cordapp project(':samples:simm-valuation-demo:contracts-states')
cordapp project(':samples:simm-valuation-demo:flows')
rpcUsers = ext.rpcUsers
extraConfig = [
custom: [
jvmArgs: ["-Xmx1g"]
@ -135,10 +133,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
address("localhost:10026")
adminAddress("localhost:10027")
}
cordapp project(':finance')
cordapp project(':samples:simm-valuation-demo:contracts-states')
cordapp project(':samples:simm-valuation-demo:flows')
rpcUsers = ext.rpcUsers
extraConfig = [
custom: [
jvmArgs: ["-Xmx1g"]
@ -154,10 +148,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
address("localhost:10036")
adminAddress("localhost:10037")
}
cordapp project(':finance')
cordapp project(':samples:simm-valuation-demo:contracts-states')
cordapp project(':samples:simm-valuation-demo:flows')
rpcUsers = ext.rpcUsers
extraConfig = [
custom: [
jvmArgs: ["-Xmx1g"]

View File

@ -16,8 +16,10 @@ import net.corda.core.identity.AbstractParty
/**
* Dummy state for use in testing. Not part of any contract, not even the [DummyContract].
*/
data class DummyState(
data class DummyState @JvmOverloads constructor (
/** Some information that the state represents for test purposes. **/
val magicNumber: Int = 0) : ContractState {
override val participants: List<AbstractParty> get() = emptyList()
val magicNumber: Int = 0,
override val participants: List<AbstractParty> = listOf()) : ContractState {
fun copy(magicNumber: Int = this.magicNumber) = DummyState(magicNumber)
}

View File

@ -27,6 +27,8 @@ import net.corda.finance.contracts.DealState
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.Obligation
import net.corda.finance.contracts.asset.OnLedgerAsset
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import net.corda.testing.core.*
import net.corda.testing.internal.chooseIdentity
import net.corda.testing.internal.chooseIdentityAndCert
@ -106,6 +108,7 @@ class VaultFiller @JvmOverloads constructor(
fun fillWithSomeTestLinearStates(numberToCreate: Int,
externalId: String? = null,
participants: List<AbstractParty> = emptyList(),
uniqueIdentifier: UniqueIdentifier? = null,
linearString: String = "",
linearNumber: Long = 0L,
linearBoolean: Boolean = false,
@ -118,7 +121,7 @@ class VaultFiller @JvmOverloads constructor(
// Issue a Linear state
val dummyIssue = TransactionBuilder(notary = defaultNotary.party).apply {
addOutputState(DummyLinearContract.State(
linearId = UniqueIdentifier(externalId),
linearId = uniqueIdentifier ?: UniqueIdentifier(externalId),
participants = participants.plus(me),
linearString = linearString,
linearNumber = linearNumber,
@ -213,6 +216,23 @@ class VaultFiller @JvmOverloads constructor(
return Vault(states)
}
/**
* Records a dummy state in the Vault (useful for creating random states when testing vault queries)
*/
fun fillWithDummyState() : Vault<DummyState> {
val outputState = TransactionState(
data = DummyState(Random().nextInt(), participants = listOf(services.myInfo.singleIdentity())),
contract = DummyContract.PROGRAM_ID,
notary = defaultNotary.party
)
val builder = TransactionBuilder()
.addOutputState(outputState)
.addCommand(DummyCommandData, defaultNotary.party.owningKey)
val stxn = services.signInitialTransaction(builder)
services.recordTransactions(stxn)
return Vault(setOf(stxn.tx.outRef(0)))
}
/**
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*/

View File

@ -0,0 +1,96 @@
package net.corda.tools.shell
import co.paralleluniverse.fibers.Suspendable
import com.google.common.io.Files
import com.jcraft.jsch.ChannelExec
import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.bouncycastle.util.io.Streams
import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertTrue
class HashLookupCommandTest {
@Ignore
@Test
fun `hash lookup command returns correct response`() {
val user = User("u", "p", setOf(Permissions.all()))
driver(DriverParameters(notarySpecs = emptyList(), extraCordappPackagesToScan = listOf("net.corda.testing.contracts"))) {
val nodeFuture = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user), startInSameProcess = true)
val node = nodeFuture.getOrThrow()
val txId = issueTransaction(node)
val session = connectToShell(user, node)
testCommand(session, command = "hashLookup ${txId.sha256()}", expected = "Found a matching transaction with Id: $txId")
testCommand(session, command = "hashLookup ${SecureHash.randomSHA256()}", expected = "No matching transaction found")
session.disconnect()
}
}
private fun testCommand(session: Session, command: String, expected: String) {
val channel = session.openChannel("exec") as ChannelExec
channel.setCommand(command)
channel.connect(5000)
assertTrue(channel.isConnected)
val response = String(Streams.readAll(channel.inputStream))
val matchFound = response.lines().any { line ->
line.contains(expected)
}
channel.disconnect()
assertTrue(matchFound)
}
private fun connectToShell(user: User, node: NodeHandle): Session {
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
user = user.username, password = user.password,
hostAndPort = node.rpcAddress,
sshdPort = 2224)
InteractiveShell.startShell(conf)
InteractiveShell.nodeInfo()
val session = JSch().getSession("u", "localhost", 2224)
session.setConfig("StrictHostKeyChecking", "no")
session.setPassword("p")
session.connect()
assertTrue(session.isConnected)
return session
}
private fun issueTransaction(node: NodeHandle): SecureHash {
return node.rpc.startFlow(::DummyIssue).returnValue.get()
}
@StartableByRPC
internal class DummyIssue : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val me = serviceHub.myInfo.legalIdentities.first().ref(0)
val fakeNotary = me.party
val builder = DummyContract.generateInitial(1, fakeNotary as Party, me)
val stx = serviceHub.signInitialTransaction(builder)
serviceHub.recordTransactions(stx)
return stx.id
}
}
}

View File

@ -0,0 +1,59 @@
package net.corda.tools.shell;
import net.corda.core.crypto.SecureHash;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.messaging.StateMachineTransactionMapping;
import org.crsh.cli.Argument;
import org.crsh.cli.Command;
import org.crsh.cli.Man;
import org.crsh.cli.Usage;
import org.crsh.text.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
public class HashLookupShellCommand extends InteractiveShellCommand {
private static Logger logger = LoggerFactory.getLogger(HashLookupShellCommand.class);
@Command
@Man("Checks if a transaction matching a specified Id hash value is recorded on this node.\n\n" +
"This is mainly intended to be used for troubleshooting notarisation issues when a\n" +
"state is claimed to be already consumed by another transaction.\n\n" +
"Example usage: hash-lookup E470FD8A6350A74217B0A99EA5FB71F091C84C64AD0DE0E72ECC10421D03AAC9"
)
public void main(@Usage("A hexadecimal SHA-256 hash value representing the hashed transaction Id") @Argument(unquote = false) String txIdHash) {
logger.info("Executing command \"hash-lookup\".");
if (txIdHash == null) {
out.println("Please provide a hexadecimal transaction Id hash value, see 'man hash-lookup'", Color.red);
return;
}
CordaRPCOps proxy = ops();
List<StateMachineTransactionMapping> mapping = proxy.stateMachineRecordedTransactionMappingSnapshot();
SecureHash txIdHashParsed;
try {
txIdHashParsed = SecureHash.parse(txIdHash);
} catch (IllegalArgumentException e) {
out.println("The provided string is not a valid hexadecimal SHA-256 hash value", Color.red);
return;
}
Optional<SecureHash> match = mapping.stream()
.map(StateMachineTransactionMapping::getTransactionId)
.filter(
txId -> SecureHash.sha256(txId.getBytes()).equals(txIdHashParsed)
)
.findFirst();
if (match.isPresent()) {
SecureHash found = match.get();
out.println("Found a matching transaction with Id: " + found.toString());
} else {
out.println("No matching transaction found", Color.red);
}
}
}

View File

@ -142,6 +142,7 @@ object InteractiveShell {
ExternalResolver.INSTANCE.addCommand("run", "Runs a method from the CordaRPCOps interface on the node.", RunShellCommand::class.java)
ExternalResolver.INSTANCE.addCommand("flow", "Commands to work with flows. Flows are how you can change the ledger.", FlowShellCommand::class.java)
ExternalResolver.INSTANCE.addCommand("start", "An alias for 'flow start'", StartShellCommand::class.java)
ExternalResolver.INSTANCE.addCommand("hashLookup", "Checks if a transaction with matching Id hash exists.", HashLookupShellCommand::class.java)
shell = ShellLifecycle(configuration.commandsDirectory).start(config, configuration.user, configuration.password)
}

View File

@ -81,7 +81,7 @@ fun main(args: Array<String>) {
log.error(e.message)
exitProcess(1)
} catch (e: Exception) {
log.error("Exception during node startup", e)
log.error("Exception during webserver startup", e)
exitProcess(1)
}
}