From 892215e27f5e85219296e5da72f34b8c79080929 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 9 Aug 2018 11:46:14 +0100 Subject: [PATCH 01/16] Clarification in upgrade docs. (#3759) * Clarification in upgrade docs. * Address review comments. --- docs/source/upgrading-cordapps.rst | 37 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/source/upgrading-cordapps.rst b/docs/source/upgrading-cordapps.rst index 33ed24ce85..ff87949cfd 100644 --- a/docs/source/upgrading-cordapps.rst +++ b/docs/source/upgrading-cordapps.rst @@ -278,8 +278,8 @@ participants agree to the proposed upgrade. The following combinations of upgrad * A state is upgraded while the contract stays the same * The state and the contract are updated simultaneously -Performing contract and state upgrades -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Performing explicit contract and state upgrades +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Preserve the existing state and contract definitions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -325,25 +325,38 @@ For example, in case of hash constraints the hash of the legacy JAR file should 3. Create the new CorDapp JAR ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Produce a new CorDapp JAR file and distribute it to all the relevant nodes. This JAR file should contain all the old -contract and state definitions, plus any new contract and state definitions. +Produce a new CorDapp JAR file. This JAR file should only contain the new contract and state definitions. -4. Restart the nodes +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 ` 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 stop their node, replace the existing CorDapp JAR with the new CorDapp JAR, and restart their -node. They may wish to do a :ref:`node drain ` first to avoid the definition of states or contracts -changing whilst a flow is in progress. +Have each node operator restart their node. -5. Authorise the upgrade +8. Authorise the upgrade ^^^^^^^^^^^^^^^^^^^^^^^^ -Once the new states and contracts are on the classpath for all the relevant nodes, the nodes must all run the +Now that new states and contracts are on the classpath for all the relevant nodes, the nodes must all 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. -6. Perform the upgrade +9. Perform the upgrade ^^^^^^^^^^^^^^^^^^^^^^ Once all nodes have performed the authorisation process, a **single** node must initiate the upgrade via the ``ContractUpgradeFlow.Initiate`` flow for each state object. This flow has the following signature: @@ -626,4 +639,4 @@ Writing enums Elements cannot be added to enums in a new version of the code. Hence, enums are only a good fit for genuinely static data that will never change (e.g. days of the week). A ``Buy`` or ``Sell`` flag is another. However, something like ``Trade Type`` or ``Currency Code`` will likely change. For those, it is preferable to choose another representation, -such as a string. \ No newline at end of file +such as a string. From 66739c138c3f856c604ab0c49a1c9295d68cf7c4 Mon Sep 17 00:00:00 2001 From: Tommy Lillehagen Date: Thu, 9 Aug 2018 18:17:20 +0100 Subject: [PATCH 02/16] NOTICK - Fix version reference for New Relic dependency (#3765) --- build.gradle | 1 + constants.properties | 1 + node/build.gradle | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 76fef2e630..4beb18f486 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,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") diff --git a/constants.properties b/constants.properties index 17c2da9942..0a06f6d094 100644 --- a/constants.properties +++ b/constants.properties @@ -10,3 +10,4 @@ artifactoryPluginVersion=4.7.3 snakeYamlVersion=1.19 caffeineVersion=2.6.2 metricsVersion=3.2.5 +metricsNewRelicVersion=1.1.1 diff --git a/node/build.gradle b/node/build.gradle index 29d6f232bf..9d37805131 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -190,7 +190,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}" } task integrationTest(type: Test) { From ce5f38104b0e4e94cd49b36d7854e14d5dd62356 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Thu, 9 Aug 2018 18:33:51 +0100 Subject: [PATCH 03/16] ENT-2168: Add a shell command to check for an existing transaction (#3762) * ENT-2168: Add a shell command to check for an existing transaction When a double-spend occurs the notary returns the hash of the consuming transaction id. I've added a 'hash-lookup' shell command that matches any recorded transactions on the node against this id hash to determine whether the state has been consumed by this node (that could happen in certain race conditions). --- .../net/corda/core/flows/NotaryError.kt | 6 +- .../tools/shell/HashLookupCommandTest.kt | 96 +++++++++++++++++++ .../tools/shell/HashLookupShellCommand.java | 59 ++++++++++++ .../net/corda/tools/shell/InteractiveShell.kt | 1 + 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tools/shell/src/integration-test/kotlin/net/corda/tools/shell/HashLookupCommandTest.kt create mode 100644 tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryError.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryError.kt index 03298f1751..91a8075b6d 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryError.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryError.kt @@ -27,9 +27,9 @@ sealed class NotaryError { /** Specifies which states have already been consumed in another transaction. */ val consumedStates: Map ) : 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. */ diff --git a/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/HashLookupCommandTest.kt b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/HashLookupCommandTest.kt new file mode 100644 index 0000000000..e679c53002 --- /dev/null +++ b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/HashLookupCommandTest.kt @@ -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() { + @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 + } + } +} \ No newline at end of file diff --git a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java new file mode 100644 index 0000000000..038e60ce0d --- /dev/null +++ b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java @@ -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 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 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); + } + } +} diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt index 21814df176..fc90960866 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt @@ -132,6 +132,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) } From b0d36b6617b295dfa5b10308e8a3096d5dabaa31 Mon Sep 17 00:00:00 2001 From: Roger Willis Date: Fri, 10 Aug 2018 08:51:56 +0100 Subject: [PATCH 04/16] * Minor formatting changes. (#3758) * NotaryServiceFlow now takes references into account when comparing number of inputs vs maxAllowedInputs. * Added reference state support for BFTSMaRt notary. * Fixes broken BFT notary tests. --- .../core/internal/notary/NotaryServiceFlow.kt | 7 +++-- .../notary/TrustedAuthorityNotaryService.kt | 9 +++++- .../BFTNonValidatingNotaryService.kt | 3 +- .../node/services/transactions/BFTSMaRt.kt | 28 +++++++++++++++---- .../transactions/NonValidatingNotaryFlow.kt | 2 +- .../transactions/ValidatingNotaryFlow.kt | 2 +- 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/NotaryServiceFlow.kt b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryServiceFlow.kt index e0a36f6dca..b6e6f49865 100644 --- a/core/src/main/kotlin/net/corda/core/internal/notary/NotaryServiceFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryServiceFlow.kt @@ -20,7 +20,7 @@ import net.corda.core.utilities.unwrap abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic() { 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 @@ -44,9 +44,10 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service: /** Checks whether the number of input states is too large. */ protected fun checkInputs(inputs: List) { - 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) } diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/TrustedAuthorityNotaryService.kt b/core/src/main/kotlin/net/corda/core/internal/notary/TrustedAuthorityNotaryService.kt index d6a3cdf5c0..1d76244380 100644 --- a/core/src/main/kotlin/net/corda/core/internal/notary/TrustedAuthorityNotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/internal/notary/TrustedAuthorityNotaryService.kt @@ -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, txId: SecureHash, caller: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List = emptyList()) { + fun commitInputStates( + inputs: List, + txId: SecureHash, + caller: Party, + requestSignature: NotarisationRequestSignature, + timeWindow: TimeWindow?, + references: List = emptyList() + ) { try { uniquenessProvider.commit(inputs, txId, caller, requestSignature, timeWindow, references) } catch (e: NotaryInternalException) { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index e6c739a9f0..b2aaac22b1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -139,10 +139,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) { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index 0fa84e0577..885746ecb6 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -223,17 +223,35 @@ object BFTSMaRt { */ abstract fun executeCommand(command: ByteArray): ByteArray? - protected fun commitInputStates(states: List, txId: SecureHash, callerName: CordaX500Name, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?) { + private fun checkConflict( + conflictingStates: LinkedHashMap, + states: List, + type: StateConsumptionDetails.ConsumedStateType + ) { + states.forEach { stateRef -> + commitLog[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.sha256(), type) } + } + } + + protected fun commitInputStates( + states: List, + txId: SecureHash, + callerName: CordaX500Name, + requestSignature: NotarisationRequestSignature, + timeWindow: TimeWindow?, + references: List = emptyList() + ) { log.debug { "Attempting to commit inputs for transaction: $txId" } services.database.transaction { logRequest(txId, callerName, requestSignature) val conflictingStates = LinkedHashMap() - 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 { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt index 45f85f0b3c..38b17af15a 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt @@ -24,7 +24,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) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt index f14447b702..a4d4ea2fb0 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt @@ -31,7 +31,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) From 839cae9872b627c9e8d7da53d9da49817cbd4890 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Fri, 10 Aug 2018 12:26:15 +0100 Subject: [PATCH 05/16] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f507347b42..bf3094165c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -157,6 +157,7 @@ see changes to this list. * Richard Crook (RBS) * Richard Gendal Brown (R3) * Richard Green (R3) +* Richard Green (Blocksure) * Rick Parker (R3) * Roberto Karpinski (Bradesco) * Robin Green (CIBC) From 9bbc85db61ea66bc71cfadb3910ad8f5026b109c Mon Sep 17 00:00:00 2001 From: David Wray Date: Fri, 10 Aug 2018 12:28:59 +0100 Subject: [PATCH 06/16] Miscellaneous doc fixes (#3760) * ENT-2298: CE Hello World Tutorial Page references Corda V1.0 Removed version number completely from text, I thought this made more sense than hardcoding a version which will almost immediately be out of date. * ENT-2302: Hello World Tutorial Page mismatch between code sample and explanatory text Updated text to proper method * ENT-2305: Java Instructions to Invoke Hello World CordApp fail Removed Java instructions --- docs/source/hello-world-flow.rst | 2 +- docs/source/hello-world-running.rst | 4 ---- docs/source/hello-world-template.rst | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/source/hello-world-flow.rst b/docs/source/hello-world-flow.rst index 817de0545a..42bf242dab 100644 --- a/docs/source/hello-world-flow.rst +++ b/docs/source/hello-world-flow.rst @@ -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 diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 1aeb15eb58..d217dee102 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -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" diff --git a/docs/source/hello-world-template.rst b/docs/source/hello-world-template.rst index 616ad7bd78..402f939fb1 100644 --- a/docs/source/hello-world-template.rst +++ b/docs/source/hello-world-template.rst @@ -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 From 68bfb7ff66423134ec6b9fa3f75010ddf1767099 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Sat, 4 Aug 2018 23:56:09 -0400 Subject: [PATCH 07/16] New jokes. --- .../net/corda/node/internal/NodeStartup.kt | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 6a5e27a3d6..3e5441a516 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -42,6 +42,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 @@ -496,8 +498,8 @@ open class NodeStartup(val args: Array) { "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! ", - "Whenever I go near my bank I get\nwithdrawal symptoms ${Emoji.coolGuy}", + "A banker left to their own devices may find\nthemselves .... a-loan! ", + "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}", @@ -508,11 +510,48 @@ open class NodeStartup(val args: Array) { "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( @@ -520,7 +559,8 @@ open class NodeStartup(val args: Array) { """ / ____/ _________/ /___ _""").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()) + } } } From 75ac13815b8c324eb88003731cbf9cb9ec951f8c Mon Sep 17 00:00:00 2001 From: Roger Willis Date: Fri, 10 Aug 2018 16:19:09 +0100 Subject: [PATCH 08/16] Updates to new resolve tx flows... (#3766) --- .../main/kotlin/net/corda/core/flows/SendTransactionFlow.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt index d9a27cbaae..d84154eb26 100644 --- a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt @@ -98,7 +98,9 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) } @Suspendable - private fun getInputTransactions(tx: SignedTransaction): Set = tx.inputs.map { it.txhash }.toSet() + private fun getInputTransactions(tx: SignedTransaction): Set { + return tx.inputs.map { it.txhash }.toSet() + tx.references.map { it.txhash }.toSet() + } private class TransactionAuthorisationFilter(private val authorisedTransactions: MutableSet = mutableSetOf(), val acceptAll: Boolean = false) { fun isAuthorised(txId: SecureHash) = acceptAll || authorisedTransactions.contains(txId) From dfea9b69408ce1f7f521654265376c8d74e1313f Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Sat, 11 Aug 2018 12:57:04 +0100 Subject: [PATCH 09/16] CORDA-1889: Update some CordFormation samples to use nodeDefaults{}. (#3771) --- constants.properties | 2 +- samples/cordapp-configuration/build.gradle | 18 +++++------ .../src/main/resources/log4j2.xml | 13 ++++++++ samples/simm-valuation-demo/build.gradle | 30 +++++++------------ 4 files changed, 33 insertions(+), 30 deletions(-) create mode 100644 samples/cordapp-configuration/src/main/resources/log4j2.xml diff --git a/constants.properties b/constants.properties index 0a06f6d094..d9f06face2 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=4.0.28 +gradlePluginsVersion=4.0.29 kotlinVersion=1.2.51 platformVersion=4 guavaVersion=25.1-jre diff --git a/samples/cordapp-configuration/build.gradle b/samples/cordapp-configuration/build.gradle index a84712f171..b1f121b9d1 100644 --- a/samples/cordapp-configuration/build.gradle +++ b/samples/cordapp-configuration/build.gradle @@ -6,14 +6,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] @@ -22,13 +25,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" @@ -42,8 +44,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") diff --git a/samples/cordapp-configuration/src/main/resources/log4j2.xml b/samples/cordapp-configuration/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..f82d09a079 --- /dev/null +++ b/samples/cordapp-configuration/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index d4d0b98caa..8fc1e65c05 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -35,8 +35,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') @@ -63,20 +63,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"] @@ -92,10 +94,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"] @@ -111,10 +109,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"] @@ -130,10 +124,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"] From 0746b1f92788b50780e65bae30ebdab518ce1c1e Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Mon, 13 Aug 2018 10:05:55 +0100 Subject: [PATCH 10/16] CORDA-1850: Minor: improve flow timeout error message (#3764) --- .../corda/node/services/statemachine/StaffedFlowHospital.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt index 75493c1cfb..1502945568 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt @@ -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) } From 0a189793077bccd4515a790b6c64cdad0300439e Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Mon, 13 Aug 2018 14:17:03 +0100 Subject: [PATCH 11/16] CORDA-1894 Remove if condition from service hub that prevents vault logic executing. (#3773) --- .../node/services/api/ServiceHubInternal.kt | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index bd85b33b2c..e8b7aa8d15 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -65,43 +65,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 }) } } } From 76072445576d5250855b6e9c42e461da6cace535 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Mon, 13 Aug 2018 16:21:16 +0100 Subject: [PATCH 12/16] [CORDA-1841]: Made paths in tutorial-cordapp consistent. (#3778) --- docs/source/tutorial-cordapp.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index 7840dc6e6a..75af66b69a 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -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: From 3b63723934088854a98cc310f4bd2d4ffe6cb9e7 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Mon, 13 Aug 2018 16:50:29 +0100 Subject: [PATCH 13/16] [CORDA-1822]: Improved error message when too many results are asked with default pagination. (#3776) --- .../net/corda/node/services/vault/NodeVaultService.kt | 6 +++--- .../kotlin/net/corda/node/services/vault/VaultQueryTests.kt | 2 +- webserver/src/main/kotlin/net/corda/webserver/WebServer.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 17f905313d..8cc9634425 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -454,9 +454,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> = mutableListOf() val statesMeta: MutableList = mutableListOf() val otherResults: MutableList = mutableListOf() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 3f8f4ef7d2..27c8a869c7 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -1176,7 +1176,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) diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt b/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt index e33adc70ff..68a537130b 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt @@ -71,7 +71,7 @@ fun main(args: Array) { 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) } } \ No newline at end of file From 166554a558e0525d0f82a280b2fb2550589485ca Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 13 Aug 2018 17:31:35 +0100 Subject: [PATCH 14/16] Fix duplicate index declaration. (#3779) --- .../test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt index 1eec85c0b3..86efb2c5c5 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt @@ -21,7 +21,7 @@ object CashSchema */ object SampleCashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { @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, From f684cb29bdfbd1cff4f15b79c658b38a738b5e67 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 13 Aug 2018 18:11:29 +0100 Subject: [PATCH 15/16] CORDA-1888 Fix Vault Query composite queries (#3775) * Reproduce composite query failures. * Fixes to OR querying and composite queries that use the same QueryCriteria (Linear, Fungible, Custom) more than once. * Revert debug logging for Hibernate SQL. * Cleanup and remove redundant joinPredicates global var. * Fix failing Java Unit test. * Fix Java compilation error in example-code section of docs. * Include copy() function for original constructor to maintain backwards API compatibility. --- .../vault/HibernateQueryCriteriaParser.kt | 53 +++++++++------ .../services/vault/VaultQueryJavaTests.java | 17 ++--- .../persistence/HibernateConfigurationTest.kt | 47 ++++++++++++++ .../node/services/vault/VaultQueryTests.kt | 64 ++++++++++++++++++- .../net/corda/testing/contracts/DummyState.kt | 8 ++- .../testing/internal/vault/VaultFiller.kt | 22 ++++++- 6 files changed, 177 insertions(+), 34 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt index e8a75c4ab5..66cce97573 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt @@ -33,7 +33,9 @@ abstract class AbstractQueryCriteriaParser, 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 @@ -173,8 +175,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class() // incrementally build list of root entities (for later use in Sort parsing) private val rootEntities = mutableMapOf, Root<*>>(Pair(VaultSchemaV1.VaultStates::class.java, vaultStates)) private val aggregateExpressions = mutableListOf>() @@ -330,8 +330,14 @@ class HibernateQueryCriteriaParser(val contractStateType: Class() - 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("stateRef"), vaultFungibleStates.get("stateRef")) predicateSet.add(joinPredicate) @@ -362,7 +368,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class - val joinLinearStateToParty = vaultFungibleStates.joinSet("participants") + val joinLinearStateToParty = vaultFungibleStates.joinSet("participants") predicateSet.add(criteriaBuilder.and(joinLinearStateToParty.`in`(participants))) criteriaQuery.distinct(true) } @@ -374,11 +380,17 @@ class HibernateQueryCriteriaParser(val contractStateType: Class() - 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("stateRef"), vaultLinearStates.get("stateRef")) - joinPredicates.add(joinPredicate) + predicateSet.add(joinPredicate) // linear ids UUID criteria.uuid?.let { @@ -407,22 +419,28 @@ class HibernateQueryCriteriaParser(val contractStateType: Class() - 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("stateRef"), entityRoot.get("stateRef")) - joinPredicates.add(joinPredicate) + predicateSet.add(joinPredicate) // resolve general criteria expressions - parseExpression(entityRoot, criteria.expression, predicateSet) + @Suppress("UNCHECKED_CAST") + parseExpression(entityRoot as Root, 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}") @@ -444,7 +462,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class("stateRef"), entityRoot.get("stateRef")) - joinPredicates.add(joinPredicate) entityRoot } when (direction) { @@ -522,7 +538,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class dealIds = asList("123", "456", "789"); - UniqueIdentifier uid = - database.transaction(tx -> { - Vault 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") @@ -338,7 +339,7 @@ public class VaultQueryJavaTests { Vault.Page snapshot = results.getSnapshot(); // DOCEND VaultJavaQueryExample5 - assertThat(snapshot.getStates()).hasSize(13); + assertThat(snapshot.getStates()).hasSize(4); return tx; }); diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index abcf7e009a..a83f7a7c3a 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -4,6 +4,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 @@ -621,6 +622,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("stateRef"), vaultLinearStates.get("stateRef")) + val joinPredicate2 = criteriaBuilder.and(criteriaBuilder.equal(vaultStates.get("stateRef"), dummyLinearStates.get("stateRef"))) + + // and predicates on VaultLinearStates + val andLinearStatesPredicate1 = criteriaBuilder.and(criteriaBuilder.equal(vaultLinearStates.get("externalId"), uniqueID456.externalId)) + val andLinearStatesPredicate2 = criteriaBuilder.and(criteriaBuilder.equal(vaultLinearStates.get("uuid"), uniqueID456.id)) + val andLinearStatesPredicate = criteriaBuilder.and(andLinearStatesPredicate1, andLinearStatesPredicate2) + + // and predicates on PersistentDummyLinearState + val andDummyLinearStatesPredicate1 = criteriaBuilder.and(criteriaBuilder.equal(dummyLinearStates.get("linearString"), "123")) + val andDummyLinearStatesPredicate2 = criteriaBuilder.and(criteriaBuilder.equal(dummyLinearStates.get("linearNumber"), 123L)) + val andDummyLinearStatesPredicate3 = criteriaBuilder.and(criteriaBuilder.equal(dummyLinearStates.get("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 */ diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 27c8a869c7..62e1b43023 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -1449,11 +1449,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 @@ -1961,6 +1962,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() + assertThat(results.states).hasSize(5) + // linear states only query + val linearStateCriteria = LinearStateQueryCriteria() + val resultsLSC = vaultService.queryBy(linearStateCriteria) + assertThat(resultsLSC.states).hasSize(2) + // fungible asset states only query + val fungibleAssetStateCriteria = FungibleAssetQueryCriteria() + val resultsFASC = vaultService.queryBy(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(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(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() + assertThat(results.states).hasSize(6) + // linear states by participants only query + val linearStateCriteria = LinearStateQueryCriteria(participants = listOf(ALICE,BOB)) + val resultsLSC = vaultService.queryBy(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(fungibleAssetStateCriteria) + assertThat(resultsFASC.states).hasSize(2) + // composite query for both linear and fungible asset states by participants + val resultsComposite = vaultService.queryBy(linearStateCriteria.or(fungibleAssetStateCriteria)) + assertThat(resultsComposite.states).hasSize(4) + } + } + @Test fun `unconsumed linear heads where external id is null`() { database.transaction { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyState.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyState.kt index 8502739712..a358427b08 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyState.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyState.kt @@ -6,8 +6,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 get() = emptyList() + val magicNumber: Int = 0, + override val participants: List = listOf()) : ContractState { + + fun copy(magicNumber: Int = this.magicNumber) = DummyState(magicNumber) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt index a3cd85d564..f5ae1f852a 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt @@ -17,6 +17,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 @@ -96,6 +98,7 @@ class VaultFiller @JvmOverloads constructor( fun fillWithSomeTestLinearStates(numberToCreate: Int, externalId: String? = null, participants: List = emptyList(), + uniqueIdentifier: UniqueIdentifier? = null, linearString: String = "", linearNumber: Long = 0L, linearBoolean: Boolean = false, @@ -108,7 +111,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, @@ -203,6 +206,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 { + 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. */ From 753d67a5197cbca4a2d094f2186d2a2d64463e53 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Tue, 14 Aug 2018 10:06:30 +0100 Subject: [PATCH 16/16] Reword index page (#3782) --- docs/source/index.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index a909055626..a706469682 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,8 +1,7 @@ Welcome to Corda ! ================== -`Corda `_ is a blockchain-inspired open source distributed ledger platform. If you’d like a -quick introduction to distributed ledgers and how Corda is different, then watch this short video: +`Corda `_ is an open-source blockchain platform. If you’d like a quick introduction to blockchains and how Corda is different, then watch this short video: .. raw:: html