diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000000..37dc57e9fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,8 @@
+Thank you for choosing to report an issue with Corda.
+
+When reporting an issue please make sure it contains;
+
+* A clear description of the issue
+* Any logs or stack traces that you can provide (use a site like https://pastebin.com for larger logs)
+* Steps to reproduce the issue
+* The version/tag/release or commit hash it occured on
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..62d77dd2d8
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,15 @@
+Thank you for choosing to contribute to Corda.
+
+Your PR must be approved by one or more reviewers and all tests must be passed on TeamCity (https://ci.corda.r3cev.com) in order to be merged.
+
+Once you have submitted a PR you are responsible for keeping it up to date until the time it is merged.
+
+PR Checklist:
+
+1. Ensure any new code is tested as described in https://docs.corda.net/testing.html
+2. Ensure you have done any relevant automated testing and manual testing
+3. Add your changes to docs/source/changelog.rst
+4. Update any documentation in docs/source relating to your changes and learn how to build them in https://docs.corda.net/building-the-docs.html
+5. If you are contributing for the first time please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you have read, and agreed to, the agreement.
+
+Please remove this message when you have read it.
diff --git a/.gitignore b/.gitignore
index 5a11950686..710d20ef3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ local.properties
**/build/*
lib/dokka.jar
+lib/quasar.jar
**/logs/*
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 75643b8eeb..4edce38213 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -62,8 +62,9 @@
-
-
+
+
+
@@ -91,13 +92,15 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 63b95c82ca..dd7a6af5da 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
// Our version: bump this on release.
- ext.corda_release_version = "0.15-SNAPSHOT"
+ ext.corda_release_version = "0.16-SNAPSHOT"
// Increment this on any release that changes public APIs anywhere in the Corda platform
// TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal
ext.corda_platform_version = 1
@@ -143,10 +143,6 @@ allprojects {
mavenLocal()
mavenCentral()
jcenter()
- // TODO: remove this once we eliminate Exposed
- maven {
- url 'https://dl.bintray.com/kotlin/exposed'
- }
maven { url 'https://jitpack.io' }
}
@@ -220,15 +216,15 @@ tasks.withType(Test) {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
- networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB"
+ networkMap "O=Controller,OU=corda,L=London,C=GB"
node {
- name "CN=Controller,O=R3,OU=corda,L=London,C=GB"
+ name "O=Controller,OU=corda,L=London,C=GB"
advertisedServices = ["corda.notary.validating"]
p2pPort 10002
cordapps = []
}
node {
- name "CN=Bank A,O=R3,OU=corda,L=London,C=GB"
+ name "O=Bank A,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10012
rpcPort 10013
@@ -236,7 +232,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
cordapps = []
}
node {
- name "CN=Bank B,O=R3,OU=corda,L=London,C=GB"
+ name "O=Bank B,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10007
rpcPort 10008
@@ -256,7 +252,7 @@ bintrayConfig {
projectUrl = 'https://github.com/corda/corda'
gpgSign = true
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
- publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver']
+ publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'
@@ -291,7 +287,7 @@ artifactory {
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
defaults {
- publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver')
+ publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver')
}
}
}
diff --git a/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
similarity index 99%
rename from client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt
rename to client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
index cf2ed02f52..a8418809da 100644
--- a/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
@@ -1,4 +1,4 @@
-package net.corda.jackson
+package net.corda.client.jackson
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
@@ -12,7 +12,7 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
-import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
@@ -27,6 +27,8 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
+import net.corda.core.utilities.parsePublicKeyBase58
+import net.corda.core.utilities.toBase58String
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.x500.X500Name
import java.math.BigDecimal
diff --git a/client/jackson/src/main/kotlin/net/corda/jackson/StringToMethodCallParser.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt
similarity index 99%
rename from client/jackson/src/main/kotlin/net/corda/jackson/StringToMethodCallParser.kt
rename to client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt
index 45454ef1d0..ddbf924c27 100644
--- a/client/jackson/src/main/kotlin/net/corda/jackson/StringToMethodCallParser.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt
@@ -1,11 +1,11 @@
-package net.corda.jackson
+package net.corda.client.jackson
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap
-import net.corda.jackson.StringToMethodCallParser.ParsedMethodCall
+import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
import org.slf4j.LoggerFactory
import java.lang.reflect.Constructor
import java.lang.reflect.Method
@@ -223,4 +223,4 @@ open class StringToMethodCallParser @JvmOverloads constructor(
Pair(name, argStr)
}.toMap()
}
-}
\ No newline at end of file
+}
diff --git a/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
similarity index 98%
rename from client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt
rename to client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
index 13acd147ab..fec036aeb2 100644
--- a/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
@@ -1,4 +1,4 @@
-package net.corda.jackson
+package net.corda.client.jackson
import com.fasterxml.jackson.databind.SerializationFeature
import net.corda.core.contracts.Amount
diff --git a/client/jackson/src/test/kotlin/net/corda/jackson/StringToMethodCallParserTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt
similarity index 98%
rename from client/jackson/src/test/kotlin/net/corda/jackson/StringToMethodCallParserTest.kt
rename to client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt
index 7610fbf91b..a93052a48e 100644
--- a/client/jackson/src/test/kotlin/net/corda/jackson/StringToMethodCallParserTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt
@@ -1,4 +1,4 @@
-package net.corda.jackson
+package net.corda.client.jackson
import net.corda.core.crypto.SecureHash
import org.junit.Assert.assertArrayEquals
@@ -65,4 +65,4 @@ class StringToMethodCallParserTest {
val args: Array = parser.parseArguments(clazz.name, names.zip(ctor.parameterTypes), "alternativeWord: Foo bar!")
assertArrayEquals(args, arrayOf("Foo bar!"))
}
-}
\ No newline at end of file
+}
diff --git a/client/jfx/build.gradle b/client/jfx/build.gradle
index 8b8c677e4b..ef9602e810 100644
--- a/client/jfx/build.gradle
+++ b/client/jfx/build.gradle
@@ -50,6 +50,7 @@ dependencies {
// Integration test helpers
integrationTestCompile "junit:junit:$junit_version"
+ integrationTestCompile project(':node-driver')
}
task integrationTest(type: Test) {
diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt
index 83453feddd..20e9c8dda5 100644
--- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt
+++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt
@@ -58,13 +58,13 @@ class NodeMonitorModelTest : DriverBasedTest() {
startFlowPermission(),
startFlowPermission())
)
- val aliceNodeFuture = startNode(ALICE.name, rpcUsers = listOf(cashUser))
- val notaryNodeFuture = startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
+ val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
+ val notaryNodeFuture = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
val notaryNodeHandle = notaryNodeFuture.getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo
notaryNode = notaryNodeHandle.nodeInfo
- newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
+ newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
@@ -76,7 +76,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false)
rpc = monitor.proxyObservable.value!!
- val bobNodeHandle = startNode(BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
+ val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
bobNode = bobNodeHandle.nodeInfo
val monitorBob = NodeMonitorModel()
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
index 243d87843e..636603d1ab 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
@@ -3,25 +3,30 @@ package net.corda.client.jfx.model
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import net.corda.core.contracts.Amount
+import java.math.BigDecimal
+import java.math.RoundingMode
import java.util.*
-
-interface ExchangeRate {
- fun rate(from: Currency, to: Currency): Double
-}
-
-fun ExchangeRate.exchangeAmount(amount: Amount, to: Currency) =
- Amount(exchangeDouble(amount, to).toLong(), to)
-
-fun ExchangeRate.exchangeDouble(amount: Amount, to: Currency) =
- rate(amount.token, to) * amount.quantity
-
/**
* This model provides an exchange rate from arbitrary currency to arbitrary currency.
- * TODO hook up an actual oracle
*/
+abstract class ExchangeRate {
+ /**
+ * Convert the given amount of a currency into the target currency.
+ *
+ * @return the original amount converted to an amount in the target currency.
+ */
+ fun exchangeAmount(amount: Amount, to: Currency) = Amount.fromDecimal(amount.toDecimal().multiply(rate(amount.token, to)), to)
+
+ abstract fun rate(from: Currency, to: Currency): BigDecimal
+}
+
+/**
+ * Default implementation of an exchange rate model, which uses a fixed exchange rate.
+ */
+// TODO hook up an actual oracle
class ExchangeRateModel {
- val exchangeRate: ObservableValue = SimpleObjectProperty(object : ExchangeRate {
- override fun rate(from: Currency, to: Currency) = 1.0
+ val exchangeRate: ObservableValue = SimpleObjectProperty(object : ExchangeRate() {
+ override fun rate(from: Currency, to: Currency) = BigDecimal.ONE
})
}
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
index ee47ed9947..923465b2b9 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
@@ -1,13 +1,14 @@
package net.corda.client.jfx.model
+import com.google.common.cache.CacheBuilder
+import com.google.common.cache.CacheLoader
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
-import net.corda.client.jfx.utils.firstOrDefault
-import net.corda.client.jfx.utils.firstOrNullObservable
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
-import net.corda.core.crypto.keys
+import net.corda.core.identity.AbstractParty
+import net.corda.core.identity.AnonymousParty
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache.MapChange
import java.security.PublicKey
@@ -29,6 +30,12 @@ class NetworkIdentityModel {
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
+ private val identityCache = CacheBuilder.newBuilder()
+ .build>(CacheLoader.from {
+ publicKey ->
+ publicKey?.let { rpcProxy.map { it?.nodeIdentityFromParty(AnonymousParty(publicKey)) } }
+ })
+
val parties: ObservableList = networkIdentities.filtered { !it.isCordaService() }
val notaries: ObservableList = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
@@ -38,8 +45,5 @@ class NetworkIdentityModel {
return advertisedServices.any { it.info.type.isNetworkMap() || it.info.type.isNotary() }
}
- // TODO: Use Identity Service in service hub instead?
- fun lookup(publicKey: PublicKey): ObservableValue = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey.keys.any { it == publicKey } }) {
- it.legalIdentity.owningKey.keys.any { it == publicKey }
- }
+ fun partyFromPublicKey(publicKey: PublicKey): ObservableValue = identityCache[publicKey]
}
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
index 33e160c742..7bd9623f52 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
@@ -89,7 +89,7 @@ class NodeMonitorModel {
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
// Transactions
- val (transactions, newTransactions) = proxy.verifiedTransactionsFeed()
+ val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed()
newTransactions.startWith(transactions).subscribe(transactionsSubject)
// SM -> TX mapping
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt
index 26387c1663..49e76e227a 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt
@@ -29,7 +29,7 @@ object AmountBindings {
return EasyBind.combine(observableCurrency, observableExchangeRate) { currency, exchangeRate ->
Pair) -> Long>(
currency,
- { (quantity, _, token) -> (exchangeRate.rate(token, currency) * quantity).toLong() }
+ { amount -> exchangeRate.exchangeAmount(amount, currency).quantity }
)
}
}
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt
index aefd494aff..17c68ac20f 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt
@@ -1,3 +1,4 @@
+@file:JvmName("ObservableFold")
package net.corda.client.jfx.utils
import javafx.application.Platform
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt
index 198fa779c5..83cb40bf70 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt
@@ -1,3 +1,4 @@
+@file:JvmName("ObservableUtilities")
package net.corda.client.jfx.utils
import javafx.application.Platform
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt
index 30c3c334b4..a751e3ac99 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt
@@ -81,15 +81,16 @@ open class ReadOnlyBackedObservableMapBase : ObservableMap {
throw UnsupportedOperationException("remove() can't be called on ReadOnlyObservableMapBase")
}
-}
-
-fun ObservableMap.createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change {
- return object : MapChangeListener.Change(this) {
- override fun getKey() = key
- override fun wasRemoved() = removedValue != null
- override fun wasAdded() = addedValue != null
- override fun getValueRemoved() = removedValue
- override fun getValueAdded() = addedValue
+ /**
+ * Construct an object modelling the given change to an observed map.
+ */
+ fun createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change {
+ return object : MapChangeListener.Change(this) {
+ override fun getKey() = key
+ override fun wasRemoved() = removedValue != null
+ override fun wasAdded() = addedValue != null
+ override fun getValueRemoved() = removedValue
+ override fun getValueAdded() = addedValue
+ }
}
}
-
diff --git a/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt b/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt
index 8da2e4e49b..f16f07445b 100644
--- a/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt
+++ b/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt
@@ -12,7 +12,7 @@ import java.util.*
* [Generator.choice] picks a generator from the specified list and runs that.
* [Generator.frequency] is similar to [choice] but the probability may be specified for each generator (it is normalised before picking).
* [Generator.combine] combines two generators of A and B with a function (A, B) -> C. Variants exist for other arities.
- * [Generator.flatMap] sequences two generators using an arbitrary A->Generator function. Keep the usage of this
+ * [Generator.flatMap] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this
* function minimal as it may explode the stack, especially when using recursion.
*
* There are other utilities as well, the type of which are usually descriptive.
@@ -32,7 +32,6 @@ import java.util.*
* The above will generate a random list of animals.
*/
class Generator(val generate: (SplittableRandom) -> Try) {
-
// Functor
fun map(function: (A) -> B): Generator =
Generator { generate(it).map(function) }
@@ -58,16 +57,42 @@ class Generator(val generate: (SplittableRandom) -> Try) {
return Generator { random -> generate(random).flatMap { function(it).generate(random) } }
}
+ fun generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
+ var error: Throwable? = null
+ for (i in 0..numberOfTries - 1) {
+ val result = generate(random)
+ error = when (result) {
+ is Try.Success -> return result.value
+ is Try.Failure -> result.exception
+ }
+ }
+ if (error == null) {
+ throw IllegalArgumentException("numberOfTries cannot be <= 0")
+ } else {
+ throw Exception("Failed to generate", error)
+ }
+ }
+
companion object {
fun pure(value: A) = Generator { Try.Success(value) }
fun impure(valueClosure: () -> A) = Generator { Try.Success(valueClosure()) }
fun fail(error: Exception) = Generator { Try.Failure(error) }
- // Alternative
+ /**
+ * Pick a generator from the specified list and run it.
+ */
fun choice(generators: List>) = intRange(0, generators.size - 1).flatMap { generators[it] }
fun success(generate: (SplittableRandom) -> A) = Generator { Try.Success(generate(it)) }
+ /**
+ * Pick a generator from the specified list, with a probability assigned to each generator, then run the
+ * chosen generator.
+ *
+ * @param generators a list of probabilities of a generator being chosen, and generators. Probabilities must be
+ * non-negative.
+ */
fun frequency(generators: List>>): Generator {
+ require(generators.all { it.first >= 0.0 }) { "Probabilities must not be negative" }
val ranges = mutableListOf>()
var current = 0.0
generators.forEach {
@@ -88,6 +113,8 @@ class Generator(val generate: (SplittableRandom) -> Try) {
}
}
+ fun frequency(vararg generators: Pair>) = frequency(generators.toList())
+
fun sequence(generators: List>) = Generator> {
val result = mutableListOf()
for (generator in generators) {
@@ -99,129 +126,113 @@ class Generator(val generate: (SplittableRandom) -> Try) {
}
Try.Success(result)
}
- }
-}
-fun Generator.Companion.frequency(vararg generators: Pair>) = frequency(generators.toList())
-
-fun Generator.generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
- var error: Throwable? = null
- for (i in 0..numberOfTries - 1) {
- val result = generate(random)
- error = when (result) {
- is Try.Success -> return result.value
- is Try.Failure -> result.exception
+ fun int() = Generator.success(SplittableRandom::nextInt)
+ fun long() = Generator.success(SplittableRandom::nextLong)
+ fun bytes(size: Int): Generator = Generator.success { random ->
+ ByteArray(size) { random.nextInt().toByte() }
}
- }
- if (error == null) {
- throw IllegalArgumentException("numberOfTries cannot be <= 0")
- } else {
- throw Exception("Failed to generate", error)
- }
-}
-fun Generator.Companion.int() = Generator.success(SplittableRandom::nextInt)
-fun Generator.Companion.long() = Generator.success(SplittableRandom::nextLong)
-fun Generator.Companion.bytes(size: Int): Generator = Generator.success { random ->
- ByteArray(size) { random.nextInt().toByte() }
-}
+ fun intRange(range: IntRange) = intRange(range.first, range.last)
+ fun intRange(from: Int, to: Int): Generator = Generator.success {
+ (from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
+ }
-fun Generator.Companion.intRange(range: IntRange) = intRange(range.first, range.last)
-fun Generator.Companion.intRange(from: Int, to: Int): Generator = Generator.success {
- (from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
-}
+ fun longRange(range: LongRange) = longRange(range.first, range.last)
+ fun longRange(from: Long, to: Long): Generator = Generator.success {
+ (from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
+ }
-fun Generator.Companion.longRange(range: LongRange) = longRange(range.first, range.last)
-fun Generator.Companion.longRange(from: Long, to: Long): Generator = Generator.success {
- (from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
-}
+ fun double() = Generator.success { it.nextDouble() }
+ fun doubleRange(from: Double, to: Double): Generator = Generator.success {
+ from + it.nextDouble() * (to - from)
+ }
-fun Generator.Companion.double() = Generator.success { it.nextDouble() }
-fun Generator.Companion.doubleRange(from: Double, to: Double): Generator = Generator.success {
- from + it.nextDouble() * (to - from)
-}
-
-fun Generator.Companion.char() = Generator {
- val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16))
- if (Character.isValidCodePoint(codePoint)) {
- return@Generator Try.Success(codePoint.toChar())
- } else {
- Try.Failure(IllegalStateException("Could not generate valid codepoint"))
- }
-}
-
-fun Generator.Companion.string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map {
- val builder = StringBuilder()
- it.forEach {
- builder.append(it)
- }
- builder.toString()
-}
-
-fun Generator.Companion.replicate(number: Int, generator: Generator): Generator> {
- val generators = mutableListOf>()
- for (i in 1..number) {
- generators.add(generator)
- }
- return sequence(generators)
-}
-
-
-fun Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator, atLeastOne: Boolean = false) = Generator> {
- val chance = (meanSize - 1) / meanSize
- val result = mutableListOf()
- var finish = false
- while (!finish) {
- val res = Generator.doubleRange(0.0, 1.0).generate(it).flatMap { value ->
- if (value < chance) {
- generator.generate(it).map { result.add(it) }
+ fun char() = Generator {
+ val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16))
+ if (Character.isValidCodePoint(codePoint)) {
+ return@Generator Try.Success(codePoint.toChar())
} else {
- finish = true
- if (result.isEmpty() && atLeastOne) {
- generator.generate(it).map { result.add(it) }
- } else Try.Success(Unit)
+ Try.Failure(IllegalStateException("Could not generate valid codepoint"))
}
}
- if (res is Try.Failure) {
- return@Generator res
+
+ fun string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map {
+ val builder = StringBuilder()
+ it.forEach {
+ builder.append(it)
+ }
+ builder.toString()
}
- }
- Try.Success(result)
-}
-fun Generator.Companion.pickOne(list: List) = Generator.intRange(0, list.size - 1).map { list[it] }
-fun Generator.Companion.pickN(number: Int, list: List) = Generator> {
- val mask = BitSet(list.size)
- val size = Math.min(list.size, number)
- for (i in 0..size - 1) {
- // mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit
- mask[i] = true
- }
- for (i in 0..list.size - 1) {
- val bit = mask[i]
- val swapIndex = i + it.nextInt(size - i)
- mask[i] = mask[swapIndex]
- mask[swapIndex] = bit
- }
- val resultList = ArrayList()
- list.forEachIndexed { index, a ->
- if (mask[index]) {
- resultList.add(a)
+ fun replicate(number: Int, generator: Generator): Generator> {
+ val generators = mutableListOf>()
+ for (i in 1..number) {
+ generators.add(generator)
+ }
+ return sequence(generators)
}
- }
- Try.Success(resultList)
-}
-fun Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
- sampleBernoulli(listOf(collection), maxRatio)
-fun Generator.Companion.sampleBernoulli(collection: Collection, meanRatio: Double = 1.0): Generator> =
- replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
+ fun replicatePoisson(meanSize: Double, generator: Generator, atLeastOne: Boolean = false) = Generator> {
+ val chance = (meanSize - 1) / meanSize
val result = mutableListOf()
- collection.forEachIndexed { index, element ->
- if (chances[index] < meanRatio) {
- result.add(element)
+ var finish = false
+ while (!finish) {
+ val res = Generator.doubleRange(0.0, 1.0).generate(it).flatMap { value ->
+ if (value < chance) {
+ generator.generate(it).map { result.add(it) }
+ } else {
+ finish = true
+ if (result.isEmpty() && atLeastOne) {
+ generator.generate(it).map { result.add(it) }
+ } else Try.Success(Unit)
+ }
+ }
+ if (res is Try.Failure) {
+ return@Generator res
}
}
- result
+ Try.Success(result)
}
+
+ fun pickOne(list: List) = Generator.intRange(0, list.size - 1).map { list[it] }
+ fun pickN(number: Int, list: List) = Generator> {
+ val mask = BitSet(list.size)
+ val size = Math.min(list.size, number)
+ for (i in 0..size - 1) {
+ // mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit
+ mask[i] = true
+ }
+ for (i in 0..list.size - 1) {
+ val bit = mask[i]
+ val swapIndex = i + it.nextInt(size - i)
+ mask[i] = mask[swapIndex]
+ mask[swapIndex] = bit
+ }
+ val resultList = ArrayList()
+ list.forEachIndexed { index, a ->
+ if (mask[index]) {
+ resultList.add(a)
+ }
+ }
+ Try.Success(resultList)
+ }
+
+ fun sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
+ sampleBernoulli(listOf(collection), maxRatio)
+
+ fun sampleBernoulli(collection: Collection, meanRatio: Double = 1.0): Generator> {
+ return replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
+ val result = mutableListOf()
+ collection.forEachIndexed { index, element ->
+ if (chances[index] < meanRatio) {
+ result.add(element)
+ }
+ }
+ result
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt b/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt
index 7b35b8d5f8..dc1315b175 100644
--- a/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt
+++ b/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt
@@ -1,3 +1,4 @@
+@file:JvmName("Generators")
package net.corda.client.mock
import net.corda.core.contracts.Amount
diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle
index 70ebc5ed57..2b3c020f49 100644
--- a/client/rpc/build.gradle
+++ b/client/rpc/build.gradle
@@ -67,7 +67,7 @@ dependencies {
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
- testCompile project(':test-utils')
+ testCompile project(':node-driver')
testCompile project(':client:mock')
// Smoke tests do NOT have any Node code on the classpath!
diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt
index 27d8157dde..e0a0c21ca0 100644
--- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt
+++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt
@@ -82,6 +82,19 @@ class RPCClientProxyHandler(
val log = loggerFor()
// To check whether toString() is being invoked
val toStringMethod: Method = Object::toString.javaMethod!!
+
+ private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) {
+ var currentThrowable = throwable
+ while (true) {
+ val cause = currentThrowable.cause
+ if (cause == null) {
+ currentThrowable.initCause(callSite)
+ break
+ } else {
+ currentThrowable = cause
+ }
+ }
+ }
}
// Used for reaping
@@ -393,6 +406,19 @@ object RpcClientObservableSerializer : Serializer>() {
return serializationContext.withProperty(RpcObservableContextKey, observableContext)
}
+ private fun pinInSubscriptions(observable: Observable, hardReferenceStore: MutableSet>): Observable {
+ val refCount = AtomicInteger(0)
+ return observable.doOnSubscribe {
+ if (refCount.getAndIncrement() == 0) {
+ require(hardReferenceStore.add(observable)) { "Reference store already contained reference $this on add" }
+ }
+ }.doOnUnsubscribe {
+ if (refCount.decrementAndGet() == 0) {
+ require(hardReferenceStore.remove(observable)) { "Reference store did not contain reference $this on remove" }
+ }
+ }
+ }
+
override fun read(kryo: Kryo, input: Input, type: Class>): Observable {
val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext
val observableId = RPCApi.ObservableId(input.readLong(true))
@@ -405,7 +431,7 @@ object RpcClientObservableSerializer : Serializer>() {
observableContext.callSiteMap?.put(observableId.toLong, rpcCallSite)
// We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users
// don't need to store a reference to the Observables themselves.
- return observable.pinInSubscriptions(observableContext.hardReferenceStore).doOnUnsubscribe {
+ return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe {
// This causes Future completions to give warnings because the corresponding OnComplete sent from the server
// will arrive after the client unsubscribes from the observable and consequently invalidates the mapping.
// The unsubscribe is due to [ObservableToFuture]'s use of first().
@@ -421,30 +447,4 @@ object RpcClientObservableSerializer : Serializer>() {
val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as Long
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
}
-}
-
-private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) {
- var currentThrowable = throwable
- while (true) {
- val cause = currentThrowable.cause
- if (cause == null) {
- currentThrowable.initCause(callSite)
- break
- } else {
- currentThrowable = cause
- }
- }
-}
-
-private fun Observable.pinInSubscriptions(hardReferenceStore: MutableSet>): Observable {
- val refCount = AtomicInteger(0)
- return this.doOnSubscribe {
- if (refCount.getAndIncrement() == 0) {
- require(hardReferenceStore.add(this)) { "Reference store already contained reference $this on add" }
- }
- }.doOnUnsubscribe {
- if (refCount.decrementAndGet() == 0) {
- require(hardReferenceStore.remove(this)) { "Reference store did not contain reference $this on remove" }
- }
- }
-}
+}
\ No newline at end of file
diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java
index 2cd3ad6628..5a8a8349eb 100644
--- a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java
+++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java
@@ -6,12 +6,12 @@ import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.messaging.FlowHandle;
import net.corda.core.node.NodeInfo;
import net.corda.core.utilities.OpaqueBytes;
+import net.corda.core.utilities.X500NameUtils;
import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow;
import net.corda.nodeapi.User;
import net.corda.smoketesting.NodeConfig;
import net.corda.smoketesting.NodeProcess;
-import org.bouncycastle.asn1.x500.X500Name;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -43,7 +43,7 @@ public class StandaloneCordaRPCJavaClientTest {
private NodeInfo notaryNode;
private NodeConfig notaryConfig = new NodeConfig(
- new X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
+ X500NameUtils.getX500Name("Notary Service", "Zurich", "CH"),
port.getAndIncrement(),
port.getAndIncrement(),
port.getAndIncrement(),
diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt
index a8c670dc2a..4fab64c88f 100644
--- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt
+++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt
@@ -9,10 +9,7 @@ import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
-import net.corda.core.utilities.OpaqueBytes
-import net.corda.core.utilities.getOrThrow
-import net.corda.core.utilities.loggerFor
-import net.corda.core.utilities.seconds
+import net.corda.core.utilities.*
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS
@@ -26,7 +23,6 @@ import net.corda.nodeapi.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import org.apache.commons.io.output.NullOutputStream
-import org.bouncycastle.asn1.x500.X500Name
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -58,12 +54,12 @@ class StandaloneCordaRPClientTest {
private lateinit var notaryNode: NodeInfo
private val notaryConfig = NodeConfig(
- legalName = X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
- p2pPort = port.andIncrement,
- rpcPort = port.andIncrement,
- webPort = port.andIncrement,
- extraServices = listOf("corda.notary.validating"),
- users = listOf(user)
+ legalName = getX500Name(O = "Notary Service", OU = "corda", L = "Zurich", C = "CH"),
+ p2pPort = port.andIncrement,
+ rpcPort = port.andIncrement,
+ webPort = port.andIncrement,
+ extraServices = listOf("corda.notary.validating"),
+ users = listOf(user)
)
@Before
diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt
new file mode 100644
index 0000000000..378a41f783
--- /dev/null
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt
@@ -0,0 +1,64 @@
+package net.corda.client.rpc
+
+import com.esotericsoftware.kryo.KryoException
+import net.corda.core.concurrent.CordaFuture
+import net.corda.core.internal.concurrent.openFuture
+import net.corda.core.messaging.*
+import net.corda.core.utilities.getOrThrow
+import net.corda.testing.rpcDriver
+import net.corda.testing.startRpcClient
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.Test
+
+class RPCFailureTests {
+ class Unserializable
+ interface Ops : RPCOps {
+ fun getUnserializable(): Unserializable
+ fun getUnserializableAsync(): CordaFuture
+ fun kotlinNPE()
+ fun kotlinNPEAsync(): CordaFuture
+ }
+
+ class OpsImpl : Ops {
+ override val protocolVersion = 1
+ override fun getUnserializable() = Unserializable()
+ override fun getUnserializableAsync(): CordaFuture {
+ return openFuture().apply { capture { getUnserializable() } }
+ }
+
+ override fun kotlinNPE() {
+ (null as Any?)!!.hashCode()
+ }
+
+ override fun kotlinNPEAsync(): CordaFuture {
+ return openFuture().apply { capture { kotlinNPE() } }
+ }
+ }
+
+ private fun rpc(proc: (Ops) -> Any?): Unit = rpcDriver {
+ val server = startRpcServer(ops = OpsImpl()).getOrThrow()
+ proc(startRpcClient(server.broker.hostAndPort!!).getOrThrow())
+ }
+
+ @Test
+ fun `kotlin NPE`() = rpc {
+ assertThatThrownBy { it.kotlinNPE() }.isInstanceOf(KotlinNullPointerException::class.java)
+ }
+
+ @Test
+ fun `kotlin NPE async`() = rpc {
+ val future = it.kotlinNPEAsync()
+ assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KotlinNullPointerException::class.java)
+ }
+
+ @Test
+ fun `unserializable`() = rpc {
+ assertThatThrownBy { it.getUnserializable() }.isInstanceOf(KryoException::class.java)
+ }
+
+ @Test
+ fun `unserializable async`() = rpc {
+ val future = it.getUnserializableAsync()
+ assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KryoException::class.java)
+ }
+}
diff --git a/config/dev/generalnodea.conf b/config/dev/generalnodea.conf
index 94b08cfc0c..16eb90f526 100644
--- a/config/dev/generalnodea.conf
+++ b/config/dev/generalnodea.conf
@@ -1,4 +1,4 @@
-myLegalName : "CN=Bank A,O=Bank A,L=London,C=GB"
+myLegalName : "O=Bank A,L=London,C=GB"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
p2pAddress : "localhost:10002"
@@ -7,6 +7,6 @@ webAddress : "localhost:10004"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:10000"
- legalName : "CN=Network Map Service,O=R3,OU=corda,L=London,C=GB"
+ legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
}
useHTTPS : false
diff --git a/config/dev/generalnodeb.conf b/config/dev/generalnodeb.conf
index 0fe9e66d9c..1eb839aece 100644
--- a/config/dev/generalnodeb.conf
+++ b/config/dev/generalnodeb.conf
@@ -7,6 +7,6 @@ webAddress : "localhost:10007"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:10000"
- legalName : "CN=Network Map Service,O=R3,OU=corda,L=London,C=GB"
+ legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
}
useHTTPS : false
diff --git a/config/dev/nameservernode.conf b/config/dev/nameservernode.conf
index 154f88aad9..664f2b6816 100644
--- a/config/dev/nameservernode.conf
+++ b/config/dev/nameservernode.conf
@@ -1,4 +1,4 @@
-myLegalName : "CN=Notary Service,O=R3,OU=corda,L=London,C=GB"
+myLegalName : "O=Notary Service,OU=corda,L=London,C=GB"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
p2pAddress : "localhost:10000"
diff --git a/constants.properties b/constants.properties
index f8c76f96a6..36e2e051b9 100644
--- a/constants.properties
+++ b/constants.properties
@@ -1,5 +1,5 @@
-gradlePluginsVersion=0.15.1
+gradlePluginsVersion=0.16.2
kotlinVersion=1.1.4
guavaVersion=21.0
bouncycastleVersion=1.57
-typesafeConfigVersion=1.3.1
\ No newline at end of file
+typesafeConfigVersion=1.3.1
diff --git a/core/build.gradle b/core/build.gradle
index ca33b3b725..655067d680 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -22,12 +22,15 @@ dependencies {
// Bring in the MockNode infrastructure for writing protocol unit tests.
testCompile project(":node")
- testCompile project(":test-utils")
+ testCompile project(":node-driver")
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+ // Quasar, for suspendable fibres.
+ compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
+
// Thread safety annotations
compile "com.google.code.findbugs:jsr305:3.0.1"
@@ -65,6 +68,15 @@ dependencies {
compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
}
+// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)
+task copyQuasarJar(type: Copy) {
+ from configurations.quasar
+ into "$project.rootProject.projectDir/lib"
+ rename { filename -> "quasar.jar"}
+}
+
+jar.finalizedBy(copyQuasarJar)
+
configurations {
testArtifacts.extendsFrom testRuntime
}
diff --git a/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt
index fd0947e1ca..548521a2e8 100644
--- a/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt
@@ -1,3 +1,4 @@
+@file:JvmName("ConcurrencyUtils")
package net.corda.core.concurrent
import net.corda.core.internal.concurrent.openFuture
diff --git a/core/src/main/kotlin/net/corda/core/contracts/Amount.kt b/core/src/main/kotlin/net/corda/core/contracts/Amount.kt
index 6de842ff63..43101b2a3e 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/Amount.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/Amount.kt
@@ -1,6 +1,6 @@
package net.corda.core.contracts
-import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.exactAdd
diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
new file mode 100644
index 0000000000..b55055e529
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
@@ -0,0 +1,42 @@
+package net.corda.core.contracts
+
+import net.corda.core.identity.Party
+import net.corda.core.internal.extractFile
+import java.io.FileNotFoundException
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.jar.JarInputStream
+
+/**
+ * An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
+ * contain public static data which can be referenced from transactions and utilised from contracts. Good examples
+ * of how attachments are meant to be used include:
+ * - Calendar data
+ * - Fixes (e.g. LIBOR)
+ * - Smart contract code
+ * - Legal documents
+ * - Facts generated by oracles which might be reused a lot
+ */
+interface Attachment : NamedByHash {
+ fun open(): InputStream
+ fun openAsJAR(): JarInputStream {
+ val stream = open()
+ try {
+ return JarInputStream(stream)
+ } catch (t: Throwable) {
+ stream.use { throw t }
+ }
+ }
+
+ /**
+ * Finds the named file case insensitively and copies it to the output stream.
+ * @throws FileNotFoundException if the given path doesn't exist in the attachment.
+ */
+ fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }
+
+ /**
+ * The parties that have correctly signed the whole attachment.
+ * Can be empty, for example non-contract attachments won't be necessarily be signed.
+ */
+ val signers: List
+}
diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt
index c1d43020f3..51036f8dfe 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt
@@ -32,33 +32,33 @@ inline fun requireThat(body: Requirements.() -> R) = Requirements.body()
// TODO: Provide a version of select that interops with Java
/** Filters the command list by type, party and public key all at once. */
-inline fun Collection>.select(signer: PublicKey? = null,
+inline fun Collection>.select(signer: PublicKey? = null,
party: AbstractParty? = null) =
filter { it.value is T }.
filter { if (signer == null) true else signer in it.signers }.
filter { if (party == null) true else party in it.signingParties }.
- map { AuthenticatedObject(it.signers, it.signingParties, it.value as T) }
+ map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
// TODO: Provide a version of select that interops with Java
/** Filters the command list by type, parties and public keys all at once. */
-inline fun Collection>.select(signers: Collection?,
+inline fun Collection>.select(signers: Collection?,
parties: Collection?) =
filter { it.value is T }.
filter { if (signers == null) true else it.signers.containsAll(signers) }.
filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
- map { AuthenticatedObject(it.signers, it.signingParties, it.value as T) }
+ map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
-inline fun Collection>.requireSingleCommand() = try {
+inline fun Collection>.requireSingleCommand() = try {
select().single()
} catch (e: NoSuchElementException) {
throw IllegalStateException("Required ${T::class.qualifiedName} command") // Better error message.
}
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
-fun Collection>.requireSingleCommand(klass: Class) =
- mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject else null }.single()
+fun Collection>.requireSingleCommand(klass: Class) =
+ mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as CommandWithParties else null }.single()
/**
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
@@ -67,7 +67,7 @@ fun Collection>.requireSingle
*/
@Throws(IllegalArgumentException::class)
inline fun verifyMoveCommand(inputs: List,
- commands: List>)
+ commands: List>)
: MoveCommand {
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
// see a signature from each of those keys. The actual signatures have been verified against the transaction
diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
index a877e98b9f..820d87fe6a 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
@@ -9,18 +9,11 @@ import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.MissingAttachmentsException
-import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
import java.security.PublicKey
import java.time.Instant
-import java.util.jar.JarInputStream
/** Implemented by anything that can be named by a secure hash value (e.g. transactions, attachments). */
interface NamedByHash {
@@ -219,11 +212,6 @@ interface LinearState : ContractState {
* except at issuance/termination.
*/
val linearId: UniqueIdentifier
-
- /**
- * True if this should be tracked by our vault(s).
- */
- fun isRelevant(ourKeys: Set): Boolean
}
// DOCEND 2
@@ -289,7 +277,7 @@ abstract class TypeOnlyCommandData : CommandData {
data class Command(val value: T, val signers: List) {
// TODO Introduce NonEmptyList?
init {
- require(signers.isNotEmpty())
+ require(signers.isNotEmpty()) { "The list of signers cannot be empty" }
}
constructor(data: T, key: PublicKey) : this(data, listOf(key))
@@ -312,9 +300,9 @@ interface MoveCommand : CommandData {
data class UpgradeCommand(val upgradedContractClass: Class>) : CommandData
// DOCSTART 6
-/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
+/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@CordaSerializable
-data class AuthenticatedObject(
+data class CommandWithParties(
val signers: List,
/** If any public keys were recognised, the looked up institutions are available here */
val signingParties: List,
@@ -367,69 +355,6 @@ interface UpgradedContract ByteArray) : Attachment {
- companion object {
- fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
- return {
- val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
- (a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
- }
- }
- }
-
- protected val attachmentData: ByteArray by lazy(dataLoader)
- override fun open(): InputStream = attachmentData.inputStream()
- override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
- override fun hashCode() = id.hashCode()
- override fun toString() = "${javaClass.simpleName}(id=$id)"
-}
-
-@Throws(IOException::class)
-fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
- val p = path.toLowerCase().split('\\', '/')
- while (true) {
- val e = nextJarEntry ?: break
- if (!e.isDirectory && e.name.toLowerCase().split('\\', '/') == p) {
- copyTo(outputTo)
- return
- }
- closeEntry()
- }
- throw FileNotFoundException(path)
-}
-
/**
* A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
similarity index 89%
rename from core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
index a1e31e2f01..3d7824751d 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
@@ -1,12 +1,8 @@
-package net.corda.core.crypto.composite
+package net.corda.core.crypto
-import net.corda.core.crypto.Crypto
-import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight
-import net.corda.core.crypto.keys
-import net.corda.core.crypto.provider.CordaObjectIdentifier
-import net.corda.core.crypto.toStringShort
-import net.corda.core.utilities.exactAdd
+import net.corda.core.crypto.CompositeKey.NodeAndWeight
import net.corda.core.serialization.CordaSerializable
+import net.corda.core.utilities.exactAdd
import net.corda.core.utilities.sequence
import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
@@ -15,8 +11,10 @@ import java.security.PublicKey
import java.util.*
/**
- * A tree data structure that enables the representation of composite public keys.
- * Notice that with that implementation CompositeKey extends PublicKey. Leaves are represented by single public keys.
+ * A tree data structure that enables the representation of composite public keys, which are used to represent
+ * the signing requirements for multisignature scenarios such as RAFT notary services. A composite key is a list
+ * of leaf keys and their contributing weight, and each leaf can be a conventional single key or a composite key.
+ * Keys contribute their weight to the total if they are matched by the signature.
*
* For complex scenarios, such as *"Both Alice and Bob need to sign to consume a state S"*, we can represent
* the requirement by creating a tree with a root [CompositeKey], and Alice and Bob as children.
@@ -26,9 +24,7 @@ import java.util.*
* Using these constructs we can express e.g. 1 of N (OR) or N of N (AND) signature requirements. By nesting we can
* create multi-level requirements such as *"either the CEO or 3 of 5 of his assistants need to sign"*.
*
- * [CompositeKey] maintains a list of [NodeAndWeight]s which holds child subtree with associated weight carried by child node signatures.
- *
- * The [threshold] specifies the minimum total weight required (in the simple case – the minimum number of child
+ * @property threshold specifies the minimum total weight required (in the simple case – the minimum number of child
* signatures required) to satisfy the sub-tree rooted at this node.
*/
@CordaSerializable
@@ -163,7 +159,7 @@ class CompositeKey private constructor(val threshold: Int, children: List
get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
@@ -253,9 +249,14 @@ class CompositeKey private constructor(val threshold: Int, children: List 0)
val n = children.size
return if (n > 1)
CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children)
@@ -265,15 +266,7 @@ class CompositeKey private constructor(val threshold: Int, children: List.expandedCompositeKeys: Set
- get() = flatMap { it.keys }.toSet()
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt
similarity index 82%
rename from core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt
index b933188d88..df7fc7c433 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt
@@ -1,11 +1,14 @@
-package net.corda.core.crypto.composite
+package net.corda.core.crypto
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.KeySpec
import java.security.spec.X509EncodedKeySpec
-class KeyFactory : KeyFactorySpi() {
+/**
+ * Factory for generating composite keys from ASN.1 format key specifications. This is used by [CordaSecurityProvider].
+ */
+class CompositeKeyFactory : KeyFactorySpi() {
@Throws(InvalidKeySpecException::class)
override fun engineGeneratePrivate(keySpec: KeySpec): PrivateKey {
@@ -23,7 +26,7 @@ class KeyFactory : KeyFactorySpi() {
@Throws(InvalidKeySpecException::class)
override fun engineGetKeySpec(key: Key, keySpec: Class): T {
- // Only support [X509EncodedKeySpec].
+ // Only support X509EncodedKeySpec.
throw InvalidKeySpecException("Not implemented yet $key $keySpec")
}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
similarity index 94%
rename from core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
index eb231cb7eb..0438a630ae 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
@@ -1,6 +1,6 @@
-package net.corda.core.crypto.composite
+package net.corda.core.crypto
-import net.corda.core.crypto.SecureHash
+import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
import net.corda.core.serialization.deserialize
import java.io.ByteArrayOutputStream
import java.security.*
@@ -27,6 +27,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
return signatureState!!
}
+ @Deprecated("Deprecated in inherited API")
@Throws(InvalidAlgorithmParameterException::class)
override fun engineGetParameter(param: String?): Any {
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
@@ -46,6 +47,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
}
}
+ @Deprecated("Deprecated in inherited API")
@Throws(InvalidAlgorithmParameterException::class)
override fun engineSetParameter(param: String?, value: Any?) {
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
diff --git a/core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
similarity index 72%
rename from core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
index 63f3abcf31..eaae5e2ffb 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
@@ -1,7 +1,5 @@
-package net.corda.core.crypto.provider
+package net.corda.core.crypto
-import net.corda.core.crypto.composite.CompositeKey
-import net.corda.core.crypto.composite.CompositeSignature
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.AccessController
import java.security.PrivilegedAction
@@ -17,20 +15,22 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
}
private fun setup() {
- put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.composite.KeyFactory")
- put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.composite.CompositeSignature")
+ put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.CompositeKeyFactory")
+ put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.CompositeSignature")
val compositeKeyOID = CordaObjectIdentifier.COMPOSITE_KEY.id
put("Alg.Alias.KeyFactory.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
put("Alg.Alias.KeyFactory.OID.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
- put("Alg.Alias.Signature.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
- put("Alg.Alias.Signature.OID.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
+
+ val compositeSignatureOID = CordaObjectIdentifier.COMPOSITE_SIGNATURE.id
+ put("Alg.Alias.Signature.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM)
+ put("Alg.Alias.Signature.OID.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM)
}
}
object CordaObjectIdentifier {
// UUID-based OID
- // TODO: Register for an OID space and issue our own shorter OID
+ // TODO: Register for an OID space and issue our own shorter OID.
@JvmField val COMPOSITE_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
@JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
index 8b1d9fb542..c5f6b16256 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -1,9 +1,5 @@
package net.corda.core.crypto
-import net.corda.core.crypto.composite.CompositeKey
-import net.corda.core.crypto.composite.CompositeSignature
-import net.corda.core.crypto.provider.CordaObjectIdentifier
-import net.corda.core.crypto.provider.CordaSecurityProvider
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
@@ -201,7 +197,7 @@ object Crypto {
private val providerMap: Map = mapOf(
BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(),
CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(),
- "BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
+ "BCPQC" to BouncyCastlePQCProvider()) // Unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
putAll(EdDSASecurityProvider())
diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
index f41fd55f6a..8e6af2cd37 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
@@ -2,8 +2,9 @@
package net.corda.core.crypto
-import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.utilities.OpaqueBytes
+import net.corda.core.utilities.toBase58
+import net.corda.core.utilities.toSHA256Bytes
import java.math.BigInteger
import net.corda.core.utilities.SgxSupport
import java.security.*
@@ -17,13 +18,9 @@ import java.security.*
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
-fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature {
- return DigitalSignature(Crypto.doSign(this, bytesToSign))
-}
+fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign))
-fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
- return DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
-}
+fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
/**
* Helper function to sign with a key pair.
@@ -81,32 +78,27 @@ fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature) : Boolean
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
-val PublicKey.keys: Set get() {
- return if (this is CompositeKey) this.leafKeys
- else setOf(this)
-}
+val PublicKey.keys: Set get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
-fun PublicKey.isFulfilledBy(otherKeys: Iterable): Boolean {
- return if (this is CompositeKey) this.isFulfilledBy(otherKeys)
- else this in otherKeys
-}
+fun PublicKey.isFulfilledBy(otherKeys: Iterable): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
-/** Checks whether any of the given [keys] matches a leaf on the CompositeKey tree or a single PublicKey */
+/** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */
fun PublicKey.containsAny(otherKeys: Iterable): Boolean {
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
else this in otherKeys
}
-/** Returns the set of all [PublicKey]s of the signatures */
+/** Returns the set of all [PublicKey]s of the signatures. */
fun Iterable.byKeys() = map { it.by }.toSet()
-// Allow Kotlin destructuring: val (private, public) = keyPair
+// Allow Kotlin destructuring:
+// val (private, public) = keyPair
operator fun KeyPair.component1(): PrivateKey = this.private
operator fun KeyPair.component2(): PublicKey = this.public
-/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
+/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */
fun generateKeyPair(): KeyPair = Crypto.generateKeyPair()
/**
@@ -147,13 +139,11 @@ fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Cr
* @param numOfBytes how many random bytes to output.
* @return a random [ByteArray].
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
- * or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
+ * or if no strong [SecureRandom] implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
* which should never happen and suggests an unusual JVM or non-standard Java library.
*/
@Throws(NoSuchAlgorithmException::class)
-fun secureRandomBytes(numOfBytes: Int): ByteArray {
- return newSecureRandom().generateSeed(numOfBytes)
-}
+fun secureRandomBytes(numOfBytes: Int): ByteArray = newSecureRandom().generateSeed(numOfBytes)
/**
* This is a hack added because during deserialisation when no-param constructors are called sometimes default values
diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
index 0e0f36c606..9e28be8a3a 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
@@ -6,8 +6,8 @@ import java.security.InvalidKeyException
import java.security.PublicKey
import java.security.SignatureException
-// TODO: Is there a use-case for bare [DigitalSignature], or is everything a [DigitalSignature.WithKey]? If there's no
-// actual use-case, we should merge the with key version into the parent class. In that case [CompositeSignatureWithKeys]
+// TODO: Is there a use-case for bare DigitalSignature, or is everything a DigitalSignature.WithKey? If there's no
+// actual use-case, we should merge the with key version into the parent class. In that case CompositeSignatureWithKeys
// should be renamed to match.
/** A wrapper around a digital signature. */
@CordaSerializable
diff --git a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt
index 831e20758e..1555ed70fc 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt
@@ -3,7 +3,7 @@ package net.corda.core.crypto
import java.util.*
/**
- * Creation and verification of a Merkle Tree for a Wire Transaction.
+ * Creation and verification of a Merkle tree for a [WireTransaction].
*
* See: https://en.wikipedia.org/wiki/Merkle_tree
*
@@ -49,7 +49,7 @@ sealed class MerkleTree {
*/
private tailrec fun buildMerkleTree(lastNodesList: List): MerkleTree {
if (lastNodesList.size == 1) {
- return lastNodesList[0] //Root reached.
+ return lastNodesList[0] // Root reached.
} else {
val newLevelHashes: MutableList = ArrayList()
val n = lastNodesList.size
diff --git a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
new file mode 100644
index 0000000000..e5533895e5
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
@@ -0,0 +1,22 @@
+package net.corda.core.crypto
+
+import net.corda.core.identity.AnonymousParty
+import net.corda.core.serialization.CordaSerializable
+import java.security.PublicKey
+
+object NullKeys {
+ @CordaSerializable
+ object NullPublicKey : PublicKey, Comparable {
+ override fun getAlgorithm() = "NULL"
+ override fun getEncoded() = byteArrayOf(0)
+ override fun getFormat() = "NULL"
+ override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
+ override fun toString() = "NULL_KEY"
+ }
+
+ val NULL_PARTY = AnonymousParty(NullPublicKey)
+
+ /** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
+ val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
index af2114ab6d..837f27faea 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
@@ -12,7 +12,7 @@ import java.security.MessageDigest
*/
@CordaSerializable
sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
- /** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes) */
+ /** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes). */
class SHA256(bytes: ByteArray) : SecureHash(bytes) {
init {
require(bytes.size == 32)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt b/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt
index a893381f7b..5027247980 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt
@@ -11,4 +11,3 @@ import net.corda.core.serialization.CordaSerializable
*/
@CordaSerializable
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)
-
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
index 9ec9d26863..0176397c2f 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
@@ -29,4 +29,5 @@ data class SignatureScheme(
val signatureName: String,
val algSpec: AlgorithmParameterSpec?,
val keySize: Int?,
- val desc: String)
+ val desc: String
+)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/X500NameUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/X500NameUtils.kt
deleted file mode 100644
index 6edd82ca93..0000000000
--- a/core/src/main/kotlin/net/corda/core/crypto/X500NameUtils.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-@file:JvmName("X500NameUtils")
-package net.corda.core.crypto
-
-import net.corda.core.internal.toX509CertHolder
-import org.bouncycastle.asn1.ASN1Encodable
-import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.asn1.x500.X500NameBuilder
-import org.bouncycastle.asn1.x500.style.BCStyle
-import org.bouncycastle.cert.X509CertificateHolder
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
-import java.security.KeyPair
-import java.security.cert.X509Certificate
-
-/**
- * Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
- * @throws IllegalArgumentException if the distinguished name does not contain a common name element.
- */
-fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
-
-/**
- * Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
- * adds one.
- * @throws IllegalArgumentException if the distinguished name does not contain a common name element.
- */
-fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
-
-/**
- * Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
- *
- * @param mutator a function to generate the new value from the previous one.
- * @throws IllegalArgumentException if the distinguished name does not contain a common name element.
- */
-private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
- val builder = X500NameBuilder(BCStyle.INSTANCE)
- var matched = false
- this.rdNs.forEach { rdn ->
- rdn.typesAndValues.forEach { typeAndValue ->
- when (typeAndValue.type) {
- BCStyle.CN -> {
- matched = true
- builder.addRDN(typeAndValue.type, mutator(typeAndValue.value))
- }
- else -> {
- builder.addRDN(typeAndValue)
- }
- }
- }
- }
- require(matched) { "Input X.500 name must include a common name (CN) attribute: ${this}" }
- return builder.build()
-}
-
-val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
-val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
-val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
-val X500Name.locationOrNull: String? get() = try {
- location
-} catch (e: Exception) {
- null
-}
-val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
-val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
-
-/**
- * Generate a distinguished name from the provided values.
- */
-@JvmOverloads
-fun getX509Name(myLegalName: String, nearestCity: String, email: String, country: String? = null): X500Name {
- return X500NameBuilder(BCStyle.INSTANCE).let { builder ->
- builder.addRDN(BCStyle.CN, myLegalName)
- builder.addRDN(BCStyle.L, nearestCity)
- country?.let { builder.addRDN(BCStyle.C, it) }
- builder.addRDN(BCStyle.E, email)
- builder.build()
- }
-}
-
-data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/testing/NullKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/testing/NullKeys.kt
deleted file mode 100644
index de2bc3ee42..0000000000
--- a/core/src/main/kotlin/net/corda/core/crypto/testing/NullKeys.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package net.corda.core.crypto.testing
-
-import net.corda.core.crypto.SignatureMetadata
-import net.corda.core.crypto.TransactionSignature
-import net.corda.core.identity.AnonymousParty
-import net.corda.core.serialization.CordaSerializable
-import java.security.PublicKey
-
-@CordaSerializable
-object NullPublicKey : PublicKey, Comparable {
- override fun getAlgorithm() = "NULL"
- override fun getEncoded() = byteArrayOf(0)
- override fun getFormat() = "NULL"
- override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
- override fun toString() = "NULL_KEY"
-}
-
-val NULL_PARTY = AnonymousParty(NullPublicKey)
-
-/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
-val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt
index c4a84da857..26e8b79874 100644
--- a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt
@@ -138,6 +138,7 @@ abstract class AbstractStateReplacementFlow {
// We use Void? instead of Unit? as that's what you'd use in Java.
abstract class Acceptor(val otherSide: Party,
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic() {
+ constructor(otherSide: Party) : this(otherSide, Acceptor.tracker())
companion object {
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
object APPROVING : ProgressTracker.Step("State replacement approved")
diff --git a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt
index 9f95d897a5..b7145a90c0 100644
--- a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt
@@ -3,7 +3,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
-import net.corda.core.crypto.toBase58String
+import net.corda.core.utilities.toBase58String
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
@@ -262,7 +262,9 @@ abstract class SignTransactionFlow(val otherParty: Party,
* @param stx a partially signed transaction received from your counter-party.
* @throws FlowException if the proposed transaction fails the checks.
*/
- @Suspendable abstract protected fun checkTransaction(stx: SignedTransaction)
+ @Suspendable
+ @Throws(FlowException::class)
+ abstract protected fun checkTransaction(stx: SignedTransaction)
@Suspendable private fun checkMySignatureRequired(stx: SignedTransaction, signingKey: PublicKey) {
require(signingKey in stx.tx.requiredSigningKeys) {
diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
index d6e7024811..c9a1cbee02 100644
--- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
@@ -1,72 +1,146 @@
package net.corda.core.flows
+import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
+import net.corda.core.identity.Party
import net.corda.core.transactions.LedgerTransaction
+import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
/**
- * A flow to be used for upgrading state objects of an old contract to a new contract.
+ * A flow to be used for authorising and upgrading state objects of an old contract to a new contract.
*
* This assembles the transaction for contract upgrade and sends out change proposals to all participants
* of that state. If participants agree to the proposed change, they each sign the transaction.
* Finally, the transaction containing all signatures is sent back to each participant so they can record it and
* use the new updated state for future transactions.
*/
-@InitiatingFlow
-@StartableByRPC
-class ContractUpgradeFlow(
- originalState: StateAndRef,
- newContractClass: Class>
-) : AbstractStateReplacementFlow.Instigator>>(originalState, newContractClass) {
+object ContractUpgradeFlow {
- companion object {
- @JvmStatic
- fun verify(tx: LedgerTransaction) {
- // Contract Upgrade transaction should have 1 input, 1 output and 1 command.
- verify(
- tx.inputStates.single(),
- tx.outputStates.single(),
- tx.commandsOfType().single())
+ /**
+ * Authorise a contract state upgrade.
+ * This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
+ * Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using the [UpgradedContract] class.
+ * This method will NOT initiate the upgrade process. To start the upgrade process, see [Initiator].
+ */
+ @StartableByRPC
+ class Authorise(
+ val stateAndRef: StateAndRef<*>,
+ private val upgradedContractClass: Class>
+ ) : FlowLogic() {
+ @Suspendable
+ override fun call(): Void? {
+ val upgrade = upgradedContractClass.newInstance()
+ if (upgrade.legacyContract != stateAndRef.state.data.contract.javaClass) {
+ throw FlowException("The contract state cannot be upgraded using provided UpgradedContract.")
+ }
+ serviceHub.contractUpgradeService.storeAuthorisedContractUpgrade(stateAndRef.ref, upgradedContractClass)
+ return null
}
- @JvmStatic
- fun verify(input: ContractState, output: ContractState, commandData: Command) {
- val command = commandData.value
- val participantKeys: Set = input.participants.map { it.owningKey }.toSet()
- val keysThatSigned: Set = commandData.signers.toSet()
- @Suppress("UNCHECKED_CAST")
- val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract
- requireThat {
- "The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
- "Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
- "Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
- "Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
+ }
+
+ /**
+ * Deauthorise a contract state upgrade.
+ * This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
+ */
+ @StartableByRPC
+ class Deauthorise(
+ val stateRef: StateRef
+ ) : FlowLogic< Void?>() {
+ @Suspendable
+ override fun call(): Void? {
+ serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
+ return null
+ }
+ }
+
+ @InitiatingFlow
+ @StartableByRPC
+ class Initiator(
+ originalState: StateAndRef,
+ newContractClass: Class>
+ ) : AbstractStateReplacementFlow.Instigator>>(originalState, newContractClass) {
+
+ companion object {
+ fun assembleBareTx(
+ stateRef: StateAndRef,
+ upgradedContractClass: Class>,
+ privacySalt: PrivacySalt
+ ): TransactionBuilder {
+ val contractUpgrade = upgradedContractClass.newInstance()
+ return TransactionBuilder(stateRef.state.notary)
+ .withItems(
+ stateRef,
+ contractUpgrade.upgrade(stateRef.state.data),
+ Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }),
+ privacySalt
+ )
}
}
- fun assembleBareTx(
- stateRef: StateAndRef,
- upgradedContractClass: Class>,
- privacySalt: PrivacySalt
- ): TransactionBuilder {
- val contractUpgrade = upgradedContractClass.newInstance()
- return TransactionBuilder(stateRef.state.notary)
- .withItems(
- stateRef,
- contractUpgrade.upgrade(stateRef.state.data),
- Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }),
- privacySalt
- )
+ @Suspendable
+ override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
+ val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
+ val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
+ // TODO: We need a much faster way of finding our key in the transaction
+ val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
+ val stx = serviceHub.signInitialTransaction(baseTx, myKey)
+ return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
}
}
- override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
- val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
- val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
- // TODO: We need a much faster way of finding our key in the transaction
- val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
- val stx = serviceHub.signInitialTransaction(baseTx, myKey)
- return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
+ @StartableByRPC
+ @InitiatedBy(ContractUpgradeFlow.Initiator::class)
+ class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor>>(otherSide) {
+
+ companion object {
+ @JvmStatic
+ fun verify(tx: LedgerTransaction) {
+ // Contract Upgrade transaction should have 1 input, 1 output and 1 command.
+ verify(tx.inputStates.single(),
+ tx.outputStates.single(),
+ tx.commandsOfType().single())
+ }
+
+ @JvmStatic
+ fun verify(input: ContractState, output: ContractState, commandData: Command) {
+ val command = commandData.value
+ val participantKeys: Set = input.participants.map { it.owningKey }.toSet()
+ val keysThatSigned: Set = commandData.signers.toSet()
+ @Suppress("UNCHECKED_CAST")
+ val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract
+ requireThat {
+ "The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
+ "Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
+ "Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
+ "Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
+ }
+ }
+ }
+
+ @Suspendable
+ @Throws(StateReplacementException::class)
+ override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal>>) {
+ // Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and
+ // verify outputs matches the proposed upgrade.
+ val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
+ requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
+ val oldStateAndRef = ourSTX!!.tx.outRef(proposal.stateRef.index)
+ val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
+ throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
+ val proposedTx = stx.tx
+ val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
+ requireThat {
+ "The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
+ "The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
+ "The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
+ }
+ ContractUpgradeFlow.Acceptor.verify(
+ oldStateAndRef.state.data,
+ expectedTx.outRef(0).state.data,
+ expectedTx.toLedgerTransaction(serviceHub).commandsOfType().single())
+ }
}
}
diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt
index 584692bd3d..74d6b2562c 100644
--- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt
@@ -72,7 +72,7 @@ object NotaryFlow {
val tx: Any = if (stx.isNotaryChangeTransaction()) {
stx.notaryChangeTx
} else {
- stx.tx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
+ stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
}
sendAndReceiveWithRetry(notaryParty, tx)
}
diff --git a/core/src/main/kotlin/net/corda/core/identity/Party.kt b/core/src/main/kotlin/net/corda/core/identity/Party.kt
index 5652fc2688..f25d6446d1 100644
--- a/core/src/main/kotlin/net/corda/core/identity/Party.kt
+++ b/core/src/main/kotlin/net/corda/core/identity/Party.kt
@@ -2,7 +2,7 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.Crypto
-import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.crypto.CompositeKey
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt
new file mode 100644
index 0000000000..2f402f43e5
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt
@@ -0,0 +1,69 @@
+package net.corda.core.internal
+
+import net.corda.core.contracts.Attachment
+import net.corda.core.crypto.SecureHash
+import net.corda.core.identity.Party
+import net.corda.core.serialization.MissingAttachmentsException
+import net.corda.core.serialization.SerializeAsTokenContext
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.security.CodeSigner
+import java.util.jar.JarInputStream
+
+abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
+ companion object {
+ fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
+ return {
+ val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
+ (a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
+ }
+ }
+
+ /** @see */
+ private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex()
+ private val shredder = ByteArray(1024)
+ }
+
+ protected val attachmentData: ByteArray by lazy(dataLoader)
+ override fun open(): InputStream = attachmentData.inputStream()
+ override val signers by lazy {
+ // Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":
+ var attachmentSigners: MutableSet? = null
+ openAsJAR().use { jar ->
+ while (true) {
+ val entry = jar.nextJarEntry ?: break
+ if (entry.isDirectory || unsignableEntryName.matches(entry.name)) continue
+ while (jar.read(shredder) != -1) { // Must read entry fully for codeSigners to be valid.
+ // Do nothing.
+ }
+ val entrySigners = entry.codeSigners ?: emptyArray()
+ attachmentSigners?.retainAll(entrySigners) ?: run { attachmentSigners = entrySigners.toMutableSet() }
+ if (attachmentSigners!!.isEmpty()) break // Performance short-circuit.
+ }
+ }
+ (attachmentSigners ?: emptySet()).map {
+ Party(it.signerCertPath.certificates[0].toX509CertHolder())
+ }.sortedBy { it.name.toString() } // Determinism.
+ }
+
+ override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
+ override fun hashCode() = id.hashCode()
+ override fun toString() = "${javaClass.simpleName}(id=$id)"
+}
+
+@Throws(IOException::class)
+fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
+ fun String.norm() = toLowerCase().split('\\', '/') // XXX: Should this really be locale-sensitive?
+ val p = path.norm()
+ while (true) {
+ val e = nextJarEntry ?: break
+ if (!e.isDirectory && e.name.norm() == p) {
+ copyTo(outputTo)
+ return
+ }
+ closeEntry()
+ }
+ throw FileNotFoundException(path)
+}
diff --git a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt
index 5006843121..ce95ee9838 100644
--- a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt
@@ -1,7 +1,6 @@
package net.corda.core.internal
import co.paralleluniverse.fibers.Suspendable
-import net.corda.core.contracts.AbstractAttachment
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.NamedByHash
import net.corda.core.crypto.SecureHash
diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
index 3133e1c3b0..6470493af9 100644
--- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
+++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
@@ -123,11 +123,11 @@ interface CordaRPCOps : RPCOps {
*
* Generic vault query function which takes a [QueryCriteria] object to define filters,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
- * and returns a [Vault.PageAndUpdates] object containing
- * 1) a snapshot as a [Vault.Page] (described previously in [queryBy])
+ * and returns a [DataFeed] object containing
+ * 1) a snapshot as a [Vault.Page] (described previously in [CordaRPCOps.vaultQueryBy])
* 2) an [Observable] of [Vault.Update]
*
- * Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
+ * Notes: the snapshot part of the query adheres to the same behaviour as the [CordaRPCOps.vaultQueryBy] function.
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
*/
// DOCSTART VaultTrackByAPI
@@ -157,15 +157,21 @@ interface CordaRPCOps : RPCOps {
// DOCEND VaultTrackAPIHelpers
/**
- * Returns a list of all recorded transactions.
+ * @suppress Returns a list of all recorded transactions.
+ *
+ * TODO This method should be removed once SGX work is finalised and the design of the corresponding API using [FilteredTransaction] can be started
*/
- fun verifiedTransactionsSnapshot(): List
+ @Deprecated("This method is intended only for internal use and will be removed from the public API soon.")
+ fun internalVerifiedTransactionsSnapshot(): List
/**
- * Returns a data feed of all recorded transactions and an observable of future recorded ones.
+ * @suppress Returns a data feed of all recorded transactions and an observable of future recorded ones.
+ *
+ * TODO This method should be removed once SGX work is finalised and the design of the corresponding API using [FilteredTransaction] can be started
*/
+ @Deprecated("This method is intended only for internal use and will be removed from the public API soon.")
@RPCReturnsObservables
- fun verifiedTransactionsFeed(): DataFeed, SignedTransaction>
+ fun internalVerifiedTransactionsFeed(): DataFeed, SignedTransaction>
/**
* Returns a snapshot list of existing state machine id - recorded transaction hash mappings.
@@ -194,14 +200,14 @@ interface CordaRPCOps : RPCOps {
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
*/
@RPCReturnsObservables
- fun startFlowDynamic(logicType: Class>, vararg args: Any?): FlowHandle
+ fun startFlowDynamic(logicType: Class>, vararg args: Any?): FlowHandle
/**
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
* result of running the flow. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
*/
@RPCReturnsObservables
- fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle
+ fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle
/**
* Returns Node's identity, assuming this will not change while the node is running.
@@ -233,20 +239,6 @@ interface CordaRPCOps : RPCOps {
*/
fun uploadAttachment(jar: InputStream): SecureHash
- /**
- * Authorise a contract state upgrade.
- * This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
- * Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
- * This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
- */
- fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class>)
-
- /**
- * Authorise a contract state upgrade.
- * This will remove the upgrade authorisation from the vault.
- */
- fun deauthoriseContractUpgrade(state: StateAndRef<*>)
-
/**
* Returns the node's current time.
*/
@@ -257,7 +249,7 @@ interface CordaRPCOps : RPCOps {
* complete with an exception if it is unable to.
*/
@RPCReturnsObservables
- fun waitUntilRegisteredWithNetworkMap(): CordaFuture
+ fun waitUntilNetworkReady(): CordaFuture
// TODO These need rethinking. Instead of these direct calls we should have a way of replicating a subset of
// the node's state locally and query that directly.
@@ -299,6 +291,11 @@ interface CordaRPCOps : RPCOps {
* @return the node info if available.
*/
fun nodeIdentityFromParty(party: AbstractParty): NodeInfo?
+
+ /**
+ * Clear all network map data from local node cache.
+ */
+ fun clearNetworkMapCache()
}
inline fun CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
@@ -322,25 +319,25 @@ inline fun CordaRPCOps.vaultTrackBy(criteria: QueryC
* Note that the passed in constructor function is only used for unification of other type parameters and reification of
* the Class instance of the flow. This could be changed to use the constructor function directly.
*/
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: () -> R
): FlowHandle = startFlowDynamic(R::class.java)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A) -> R,
arg0: A
): FlowHandle = startFlowDynamic(R::class.java, arg0)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B) -> R,
arg0: A,
arg1: B
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C) -> R,
arg0: A,
@@ -348,7 +345,7 @@ inline fun > CordaRPCOps.startFlow(
arg2: C
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C, D) -> R,
arg0: A,
@@ -357,7 +354,7 @@ inline fun > CordaRPCOps.startFlow
arg3: D
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C, D, E) -> R,
arg0: A,
@@ -367,7 +364,7 @@ inline fun > CordaRPCOps.startF
arg4: E
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C, D, E, F) -> R,
arg0: A,
@@ -382,20 +379,20 @@ inline fun > CordaRPCOps.sta
* Same again, except this time with progress-tracking enabled.
*/
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: () -> R
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A) -> R,
arg0: A
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B) -> R,
arg0: A,
@@ -403,7 +400,7 @@ inline fun > CordaRPCOps.startTrackedFlo
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C) -> R,
arg0: A,
@@ -412,7 +409,7 @@ inline fun > CordaRPCOps.startTracked
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C, D) -> R,
arg0: A,
@@ -422,7 +419,7 @@ inline fun > CordaRPCOps.startTrac
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
@Suppress("unused")
-inline fun