mirror of
https://github.com/corda/corda.git
synced 2025-03-15 00:36:49 +00:00
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:
commit
237c133804
205
CONTRIBUTORS.md
205
CONTRIBUTORS.md
@ -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)
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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. */
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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") {
|
||||
|
@ -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())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
13
samples/cordapp-configuration/src/main/resources/log4j2.xml
Normal file
13
samples/cordapp-configuration/src/main/resources/log4j2.xml
Normal 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>
|
@ -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"]
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user