diff --git a/.gitignore b/.gitignore
index b2f09d9180..8fc24e7bad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -88,6 +88,8 @@ docs/virtualenv/
# vim
*.swp
+*.swn
+*.swo
# Files you may find useful to have in your working directory.
PLAN
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 4647555bea..175866f66d 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -91,6 +91,8 @@
+
+
diff --git a/TRADEMARK b/TRADEMARK
index be0b5c8260..aa2799e5d3 100644
--- a/TRADEMARK
+++ b/TRADEMARK
@@ -1,4 +1,4 @@
-Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates.
+Corda and the Corda logo are trademarks of R3 HoldCo LLC and its affiliates.
All rights reserved.
-For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy available at https://www.r3.com/trademark-usage-policy
\ No newline at end of file
+For R3 HoldCo LLC's trademark and logo usage information, please consult our Trademark Usage Policy available at https://www.r3.com/trademark-policy
diff --git a/build.gradle b/build.gradle
index 4515bb43da..4904203fc2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,11 +1,10 @@
-
buildscript {
// For sharing constants between builds
Properties constants = new Properties()
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
// Our version: bump this on release.
- ext.corda_release_version = "0.13-SNAPSHOT"
+ ext.corda_release_version = "0.14-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
@@ -75,6 +74,7 @@ plugins {
// but the DSL has some restrictions e.g can't be used on the allprojects section. So we should revisit this if there are improvements in Gradle.
// Version 1.0.2 of this plugin uses capsule:1.0.1
id "us.kirchmeier.capsule" version "1.0.2"
+ id "com.jfrog.artifactory" version "4.4.18"
}
ext {
@@ -85,6 +85,7 @@ apply plugin: 'project-report'
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.cordformation'
+apply plugin: 'maven-publish'
// We need the following three lines even though they're inside an allprojects {} block below because otherwise
// IntelliJ gets confused when importing the project and ends up erasing and recreating the .idea directory, along
@@ -103,12 +104,6 @@ allprojects {
sourceCompatibility = 1.8
targetCompatibility = 1.8
- // Use manual resource copying of log4j2.xml rather than source sets.
- // This prevents problems in IntelliJ with regard to duplicate source roots.
- processTestResources {
- from file("$rootDir/config/test/log4j2.xml")
- }
-
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Xlint:-options" << "-parameters"
}
@@ -254,7 +249,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', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-node-schemas', '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-node-schemas', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'
@@ -279,3 +274,17 @@ task buildCordappDependenciesZip(type: Zip) {
from 'node/capsule/NOTICE' // CDDL notice
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
+
+artifactory {
+ publish {
+ contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
+ repository {
+ repoKey = 'corda-releases'
+ username = 'teamcity'
+ 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-node-schemas', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver')
+ }
+ }
+}
diff --git a/client/jackson/build.gradle b/client/jackson/build.gradle
index 818b742f5d..234f7e1ae0 100644
--- a/client/jackson/build.gradle
+++ b/client/jackson/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
+apply plugin: 'com.jfrog.artifactory'
dependencies {
compile project(':core')
diff --git a/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt
index eaaeff5306..1ebb881d19 100644
--- a/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt
+++ b/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt
@@ -10,13 +10,14 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
import net.corda.contracts.BusinessCalendar
import net.corda.core.contracts.Amount
import net.corda.core.crypto.*
+import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAPublicKey
diff --git a/client/jfx/build.gradle b/client/jfx/build.gradle
index 69f39972c6..5d12e01f56 100644
--- a/client/jfx/build.gradle
+++ b/client/jfx/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
+apply plugin: 'com.jfrog.artifactory'
description 'Corda client JavaFX modules'
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 678f4b3a50..5c93d9e820 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
@@ -12,19 +12,19 @@ import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.StateMachineRunId
import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
+import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.ServiceInfo
-import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
-import net.corda.core.utilities.ALICE
-import net.corda.core.utilities.BOB
-import net.corda.core.utilities.CHARLIE
-import net.corda.core.utilities.DUMMY_NOTARY
+import net.corda.testing.ALICE
+import net.corda.testing.BOB
+import net.corda.testing.CHARLIE
+import net.corda.testing.DUMMY_NOTARY
import net.corda.flows.CashExitFlow
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
@@ -113,11 +113,13 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test
fun `cash issue works end to end`() {
+ val anonymous = false
rpc.startFlow(::CashIssueFlow,
Amount(100, USD),
OpaqueBytes(ByteArray(1, { 1 })),
aliceNode.legalIdentity,
- notaryNode.notaryIdentity
+ notaryNode.notaryIdentity,
+ anonymous
)
vaultUpdates.expectEvents(isStrict = false) {
@@ -138,8 +140,9 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test
fun `cash issue and move`() {
- rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), aliceNode.legalIdentity, notaryNode.notaryIdentity).returnValue.getOrThrow()
- rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity).returnValue.getOrThrow()
+ val anonymous = false
+ rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), aliceNode.legalIdentity, notaryNode.notaryIdentity, anonymous).returnValue.getOrThrow()
+ rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity, anonymous).returnValue.getOrThrow()
var issueSmId: StateMachineRunId? = null
var moveSmId: StateMachineRunId? = null
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 8505d1c621..e2b134bc8c 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
@@ -1,18 +1,18 @@
package net.corda.client.jfx.model
-import com.google.common.net.HostAndPort
import javafx.beans.property.SimpleObjectProperty
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineInfo
+import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.node.services.NetworkMapCache.MapChange
-import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.seconds
import net.corda.core.transactions.SignedTransaction
+import net.corda.core.utilities.NetworkHostAndPort
import rx.Observable
import rx.subjects.PublishSubject
@@ -51,7 +51,7 @@ class NodeMonitorModel {
* Register for updates to/from a given vault.
* TODO provide an unsubscribe mechanism
*/
- fun register(nodeHostAndPort: HostAndPort, username: String, password: String) {
+ fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String) {
val client = CordaRPCClient(
hostAndPort = nodeHostAndPort,
configuration = CordaRPCClientConfiguration.default.copy(
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/StateMachineDataModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/StateMachineDataModel.kt
index 139506d258..91167af17d 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/StateMachineDataModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/StateMachineDataModel.kt
@@ -7,17 +7,17 @@ import javafx.collections.FXCollections
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
import net.corda.client.jfx.utils.recordAsAssociation
-import net.corda.core.ErrorOr
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.StateMachineUpdate
+import net.corda.core.utilities.Try
import org.fxmisc.easybind.EasyBind
data class ProgressStatus(val status: String?)
sealed class StateMachineStatus {
data class Added(val id: StateMachineRunId, val stateMachineName: String, val flowInitiator: FlowInitiator) : StateMachineStatus()
- data class Removed(val id: StateMachineRunId, val result: ErrorOr<*>) : StateMachineStatus()
+ data class Removed(val id: StateMachineRunId, val result: Try<*>) : StateMachineStatus()
}
data class StateMachineData(
@@ -33,11 +33,11 @@ data class Counter(
var progress: SimpleIntegerProperty = SimpleIntegerProperty(0)
) {
fun addSmm() { progress.value += 1 }
- fun removeSmm(result: ErrorOr<*>) {
+ fun removeSmm(result: Try<*>) {
progress.value -= 1
- when (result.error) {
- null -> success.value += 1
- else -> errored.value += 1
+ when (result) {
+ is Try.Success -> success.value += 1
+ is Try.Failure -> errored.value += 1
}
}
}
diff --git a/client/mock/build.gradle b/client/mock/build.gradle
index 3b278fcbba..d709d4c911 100644
--- a/client/mock/build.gradle
+++ b/client/mock/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
+apply plugin: 'com.jfrog.artifactory'
description 'Corda client mock modules'
diff --git a/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt b/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt
index 771fca5fd1..6bd7b68e7c 100644
--- a/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt
+++ b/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt
@@ -4,7 +4,7 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.GBP
import net.corda.core.contracts.USD
import net.corda.core.identity.Party
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import net.corda.flows.CashFlowCommand
import java.util.*
@@ -26,7 +26,7 @@ open class EventGenerator(val parties: List, val currencies: List
addToMap(ccy, amount)
- CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary)
+ CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary, anonymous = true)
}
protected val exitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator) { amount, issueRef, ccy ->
@@ -35,7 +35,7 @@ open class EventGenerator(val parties: List, val currencies: List
- CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
+ CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient, anonymous = true)
}
open val issuerGenerator = Generator.frequency(listOf(
@@ -71,11 +71,11 @@ class ErrorFlowsEventGenerator(parties: List, currencies: List,
}
val normalMoveGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
- CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
+ CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient, anonymous = true)
}
val errorMoveGenerator = partyGenerator.combine(currencyGenerator) { recipient, currency ->
- CashFlowCommand.PayCash(Amount(currencyMap[currency]!! * 2, currency), recipient)
+ CashFlowCommand.PayCash(Amount(currencyMap[currency]!! * 2, currency), recipient, anonymous = true)
}
override val moveCashGenerator = Generator.frequency(listOf(
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 74c052e723..9748e2a2ca 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
@@ -1,7 +1,7 @@
package net.corda.client.mock
import net.corda.client.mock.Generator.Companion.choice
-import net.corda.core.ErrorOr
+import net.corda.core.utilities.Try
import java.util.*
/**
@@ -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.bind] sequences two generators using an arbitrary A->Generator function. Keep the usage of this
+ * [Generator.flatMap] sequences two generators using an arbitrary A->Generator 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.
@@ -31,7 +31,7 @@ import java.util.*
*
* The above will generate a random list of animals.
*/
-class Generator(val generate: (SplittableRandom) -> ErrorOr) {
+class Generator(val generate: (SplittableRandom) -> Try) {
// Functor
fun map(function: (A) -> B): Generator =
@@ -54,18 +54,19 @@ class Generator(val generate: (SplittableRandom) -> ErrorOr) {
product(other1.product(other2.product(other3.product(other4.product(pure({ e -> { d -> { c -> { b -> { a -> function(a, b, c, d, e) } } } } }))))))
// Monad
- fun bind(function: (A) -> Generator) =
- Generator { generate(it).bind { a -> function(a).generate(it) } }
+ fun flatMap(function: (A) -> Generator): Generator {
+ return Generator { random -> generate(random).flatMap { function(it).generate(random) } }
+ }
companion object {
- fun pure(value: A) = Generator { ErrorOr(value) }
- fun impure(valueClosure: () -> A) = Generator { ErrorOr(valueClosure()) }
- fun fail(error: Exception) = Generator { ErrorOr.of(error) }
+ 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
- fun choice(generators: List>) = intRange(0, generators.size - 1).bind { generators[it] }
+ fun choice(generators: List>) = intRange(0, generators.size - 1).flatMap { generators[it] }
- fun success(generate: (SplittableRandom) -> A) = Generator { ErrorOr(generate(it)) }
+ fun success(generate: (SplittableRandom) -> A) = Generator { Try.Success(generate(it)) }
fun frequency(generators: List>>): Generator {
val ranges = mutableListOf>()
var current = 0.0
@@ -74,11 +75,11 @@ class Generator(val generate: (SplittableRandom) -> ErrorOr) {
ranges.add(Pair(current, next))
current = next
}
- return doubleRange(0.0, current).bind { value ->
- generators[ranges.binarySearch { range ->
- if (value < range.first) {
+ return doubleRange(0.0, current).flatMap { value ->
+ generators[ranges.binarySearch { (first, second) ->
+ if (value < first) {
1
- } else if (value < range.second) {
+ } else if (value < second) {
0
} else {
-1
@@ -91,14 +92,12 @@ class Generator(val generate: (SplittableRandom) -> ErrorOr) {
val result = mutableListOf()
for (generator in generators) {
val element = generator.generate(it)
- val v = element.value
- if (v != null) {
- result.add(v)
- } else {
- return@Generator ErrorOr.of(element.error!!)
+ when (element) {
+ is Try.Success -> result.add(element.value)
+ is Try.Failure -> return@Generator element
}
}
- ErrorOr(result)
+ Try.Success(result)
}
}
}
@@ -109,11 +108,9 @@ fun Generator.generateOrFail(random: SplittableRandom, numberOfTries: Int
var error: Throwable? = null
for (i in 0..numberOfTries - 1) {
val result = generate(random)
- val v = result.value
- if (v != null) {
- return v
- } else {
- error = result.error
+ error = when (result) {
+ is Try.Success -> return result.value
+ is Try.Failure -> result.exception
}
}
if (error == null) {
@@ -147,9 +144,9 @@ fun Generator.Companion.doubleRange(from: Double, to: Double): Generator
fun Generator.Companion.char() = Generator {
val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16))
if (Character.isValidCodePoint(codePoint)) {
- return@Generator ErrorOr(codePoint.toChar())
+ return@Generator Try.Success(codePoint.toChar())
} else {
- ErrorOr.of(IllegalStateException("Could not generate valid codepoint"))
+ Try.Failure(IllegalStateException("Could not generate valid codepoint"))
}
}
@@ -175,20 +172,19 @@ fun Generator.Companion.replicatePoisson(meanSize: Double, generator: Genera
val result = mutableListOf()
var finish = false
while (!finish) {
- val errorOr = Generator.doubleRange(0.0, 1.0).generate(it).bind { value ->
+ val result = Generator.doubleRange(0.0, 1.0).generate(it).flatMap { value ->
if (value < chance) {
generator.generate(it).map { result.add(it) }
} else {
finish = true
- ErrorOr(Unit)
+ Try.Success(Unit)
}
}
- val e = errorOr.error
- if (e != null) {
- return@Generator ErrorOr.of(e)
+ if (result is Try.Failure) {
+ return@Generator result
}
}
- ErrorOr(result)
+ Try.Success(result)
}
fun Generator.Companion.pickOne(list: List) = Generator.intRange(0, list.size - 1).map { list[it] }
@@ -211,7 +207,7 @@ fun Generator.Companion.pickN(number: Int, list: List) = Generator Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
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 dd766cc4c9..7b35b8d5f8 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,7 +1,7 @@
package net.corda.client.mock
import net.corda.core.contracts.Amount
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import java.util.*
fun generateCurrency(): Generator {
diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle
index b18b563b0f..b2ab10dff4 100644
--- a/client/rpc/build.gradle
+++ b/client/rpc/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
+apply plugin: 'com.jfrog.artifactory'
description 'Corda client RPC modules'
@@ -36,10 +37,7 @@ sourceSets {
}
processSmokeTestResources {
- from(file("$rootDir/config/test/log4j2.xml")) {
- rename 'log4j2\\.xml', 'log4j2-test.xml'
- }
- from(project(':node:capsule').tasks.buildCordaJAR) {
+ from(project(':node:capsule').tasks['buildCordaJAR']) {
rename 'corda-(.*)', 'corda.jar'
}
}
@@ -85,4 +83,4 @@ jar {
publish {
name = jar.baseName
-}
\ No newline at end of file
+}
diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt
index 72f3ef6316..40a179fd29 100644
--- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt
+++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt
@@ -5,9 +5,9 @@ import net.corda.core.flows.FlowInitiator
import net.corda.core.getOrThrow
import net.corda.core.messaging.*
import net.corda.core.node.services.ServiceInfo
-import net.corda.core.random63BitValue
-import net.corda.core.serialization.OpaqueBytes
-import net.corda.core.utilities.ALICE
+import net.corda.core.crypto.random63BitValue
+import net.corda.core.utilities.OpaqueBytes
+import net.corda.testing.ALICE
import net.corda.flows.CashException
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt
index 4cf43156d2..ac524995c7 100644
--- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt
+++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt
@@ -5,17 +5,22 @@ import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.pool.KryoPool
-import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import net.corda.client.rpc.internal.RPCClient
import net.corda.client.rpc.internal.RPCClientConfiguration
-import net.corda.core.*
+import net.corda.core.crypto.random63BitValue
+import net.corda.core.future
+import net.corda.core.getOrThrow
import net.corda.core.messaging.RPCOps
-import net.corda.testing.driver.poll
+import net.corda.core.millis
+import net.corda.core.seconds
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.Try
import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.RPCKryo
import net.corda.testing.*
+import net.corda.testing.driver.poll
import org.apache.activemq.artemis.api.core.SimpleString
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -24,7 +29,10 @@ import rx.Observable
import rx.subjects.PublishSubject
import rx.subjects.UnicastSubject
import java.time.Duration
-import java.util.concurrent.*
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
class RPCStabilityTests {
@@ -77,9 +85,9 @@ class RPCStabilityTests {
val executor = Executors.newScheduledThreadPool(1)
fun startAndStop() {
rpcDriver {
- ErrorOr.catch { startRpcClient(HostAndPort.fromString("localhost:9999")).get() }
+ Try.on { startRpcClient(NetworkHostAndPort("localhost", 9999)).get() }
val server = startRpcServer(ops = DummyOps)
- ErrorOr.catch { startRpcClient(
+ Try.on { startRpcClient(
server.get().broker.hostAndPort!!,
configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1)
).get() }
diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt
index 199cdd6d67..78baa8d906 100644
--- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt
+++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt
@@ -1,9 +1,9 @@
package net.corda.client.rpc
-import com.google.common.net.HostAndPort
import net.corda.client.rpc.internal.RPCClient
import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.core.messaging.CordaRPCOps
+import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.config.SSLConfiguration
@@ -33,7 +33,7 @@ data class CordaRPCClientConfiguration(
/** @see RPCClient */
class CordaRPCClient(
- hostAndPort: HostAndPort,
+ hostAndPort: NetworkHostAndPort,
sslConfiguration: SSLConfiguration? = null,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default
) {
diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt
index 3e52dbd946..a792c24faa 100644
--- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt
+++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt
@@ -1,11 +1,11 @@
package net.corda.client.rpc.internal
-import com.google.common.net.HostAndPort
import net.corda.core.logElapsedTime
import net.corda.core.messaging.RPCOps
import net.corda.core.minutes
-import net.corda.core.random63BitValue
+import net.corda.core.crypto.random63BitValue
import net.corda.core.seconds
+import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
@@ -88,7 +88,7 @@ class RPCClient(
val rpcConfiguration: RPCClientConfiguration = RPCClientConfiguration.default
) {
constructor(
- hostAndPort: HostAndPort,
+ hostAndPort: NetworkHostAndPort,
sslConfiguration: SSLConfiguration? = null,
configuration: RPCClientConfiguration = RPCClientConfiguration.default
) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration)
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 86dcf7a7d1..e83363b7fc 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
@@ -12,9 +12,9 @@ import com.google.common.cache.RemovalListener
import com.google.common.util.concurrent.SettableFuture
import com.google.common.util.concurrent.ThreadFactoryBuilder
import net.corda.core.ThreadBox
+import net.corda.core.crypto.random63BitValue
import net.corda.core.getOrThrow
import net.corda.core.messaging.RPCOps
-import net.corda.core.random63BitValue
import net.corda.core.serialization.KryoPoolWithContext
import net.corda.core.utilities.*
import net.corda.nodeapi.*
@@ -229,14 +229,15 @@ class RPCClientProxyHandler(
if (replyFuture == null) {
log.error("RPC reply arrived to unknown RPC ID ${serverToClient.id}, this indicates an internal RPC error.")
} else {
- val rpcCallSite = callSiteMap?.get(serverToClient.id.toLong)
- serverToClient.result.match(
- onError = {
- if (rpcCallSite != null) addRpcCallSiteToThrowable(it, rpcCallSite)
- replyFuture.setException(it)
- },
- onValue = { replyFuture.set(it) }
- )
+ val result = serverToClient.result
+ when (result) {
+ is Try.Success -> replyFuture.set(result.value)
+ is Try.Failure -> {
+ val rpcCallSite = callSiteMap?.get(serverToClient.id.toLong)
+ if (rpcCallSite != null) addRpcCallSiteToThrowable(result.exception, rpcCallSite)
+ replyFuture.setException(result.exception)
+ }
+ }
}
}
is RPCApi.ServerToClient.Observation -> {
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 3c0c055655..182ca10eea 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
@@ -10,17 +10,13 @@ import net.corda.core.contracts.POUNDS
import net.corda.core.contracts.SWISS_FRANCS
import net.corda.core.crypto.SecureHash
import net.corda.core.getOrThrow
-import net.corda.core.identity.Party
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.PageSpecification
-import net.corda.core.node.services.vault.QueryCriteria
-import net.corda.core.node.services.vault.Sort
-import net.corda.core.node.services.vault.SortAttribute
+import net.corda.core.node.services.vault.*
import net.corda.core.seconds
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import net.corda.core.sizedInputStreamAndHash
-import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.loggerFor
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
@@ -28,6 +24,7 @@ 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
@@ -51,10 +48,10 @@ class StandaloneCordaRPClientTest {
private lateinit var notary: NodeProcess
private lateinit var rpcProxy: CordaRPCOps
private lateinit var connection: CordaRPCConnection
- private lateinit var notaryIdentity: Party
+ private lateinit var notaryNode: NodeInfo
private val notaryConfig = NodeConfig(
- party = DUMMY_NOTARY,
+ legalName = X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
p2pPort = port.andIncrement,
rpcPort = port.andIncrement,
webPort = port.andIncrement,
@@ -67,7 +64,7 @@ class StandaloneCordaRPClientTest {
notary = NodeProcess.Factory().create(notaryConfig)
connection = notary.connect()
rpcProxy = connection.proxy
- notaryIdentity = fetchNotaryIdentity()
+ notaryNode = fetchNotaryIdentity()
}
@After
@@ -95,7 +92,7 @@ class StandaloneCordaRPClientTest {
@Test
fun `test starting flow`() {
- rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
+ rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout)
}
@@ -103,7 +100,7 @@ class StandaloneCordaRPClientTest {
fun `test starting tracked flow`() {
var trackCount = 0
val handle = rpcProxy.startTrackedFlow(
- ::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity
+ ::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity
)
handle.progress.subscribe { msg ->
log.info("Flow>> $msg")
@@ -115,7 +112,7 @@ class StandaloneCordaRPClientTest {
@Test
fun `test network map`() {
- assertEquals(DUMMY_NOTARY.name, notaryIdentity.name)
+ assertEquals(notaryConfig.legalName, notaryNode.legalIdentity.name)
}
@Test
@@ -132,38 +129,15 @@ class StandaloneCordaRPClientTest {
}
// Now issue some cash
- rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
+ rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout)
assertEquals(1, updateCount)
}
- @Test
- fun `test vault`() {
- val (vault, vaultUpdates) = rpcProxy.vaultAndUpdates()
- assertEquals(0, vault.size)
-
- var updateCount = 0
- vaultUpdates.subscribe { update ->
- log.info("Vault>> FlowId=${update.flowId}")
- ++updateCount
- }
-
- // Now issue some cash
- rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
- .returnValue.getOrThrow(timeout)
- assertNotEquals(0, updateCount)
-
- // Check that this cash exists in the vault
- val cashBalance = rpcProxy.getCashBalances()
- log.info("Cash Balances: $cashBalance")
- assertEquals(1, cashBalance.size)
- assertEquals(629.POUNDS, cashBalance[Currency.getInstance("GBP")])
- }
-
@Test
fun `test vault track by`() {
val (vault, vaultUpdates) = rpcProxy.vaultTrackBy()
- assertEquals(0, vault.totalStatesAvailable)
+ assertEquals(0, vault.states.size)
var updateCount = 0
vaultUpdates.subscribe { update ->
@@ -172,7 +146,7 @@ class StandaloneCordaRPClientTest {
}
// Now issue some cash
- rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
+ rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout)
assertNotEquals(0, updateCount)
@@ -186,18 +160,18 @@ class StandaloneCordaRPClientTest {
@Test
fun `test vault query by`() {
// Now issue some cash
- rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
+ rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.legalIdentity, notaryNode.notaryIdentity)
.returnValue.getOrThrow(timeout)
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
- val paging = PageSpecification(0, 10)
+ val paging = PageSpecification(DEFAULT_PAGE_NUM, 10)
val sorting = Sort(setOf(Sort.SortColumn(SortAttribute.Standard(Sort.VaultStateAttribute.RECORDED_TIME), Sort.Direction.DESC)))
val queryResults = rpcProxy.vaultQueryBy(criteria, paging, sorting)
assertEquals(1, queryResults.totalStatesAvailable)
assertEquals(queryResults.states.first().state.data.amount.quantity, 629.POUNDS.quantity)
- rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryIdentity).returnValue.getOrThrow()
+ rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNode.legalIdentity).returnValue.getOrThrow()
val moreResults = rpcProxy.vaultQueryBy(criteria, paging, sorting)
assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100
@@ -209,11 +183,11 @@ class StandaloneCordaRPClientTest {
assertEquals(629.POUNDS, cashBalance[Currency.getInstance("GBP")])
}
- private fun fetchNotaryIdentity(): Party {
- val (nodeInfo, nodeUpdates) = rpcProxy.networkMapUpdates()
+ private fun fetchNotaryIdentity(): NodeInfo {
+ val (nodeInfo, nodeUpdates) = rpcProxy.networkMapFeed()
nodeUpdates.notUsed()
assertEquals(1, nodeInfo.size)
- return nodeInfo[0].legalIdentity
+ return nodeInfo[0]
}
// This InputStream cannot have been whitelisted.
diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/ValidateClasspathTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/ValidateClasspathTest.kt
new file mode 100644
index 0000000000..ecc534ca8a
--- /dev/null
+++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/ValidateClasspathTest.kt
@@ -0,0 +1,27 @@
+package net.corda.kotlin.rpc
+
+import net.corda.core.div
+import org.junit.Test
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class ValidateClasspathTest {
+ @Test
+ fun `node not on classpath`() {
+ val paths = System.getProperty("java.class.path").split(File.pathSeparatorChar).map { Paths.get(it) }
+ // First find core so that if node is there, it's in the form we expect:
+ assertFalse(paths.filter { it.contains("core" / "build") }.isEmpty())
+ assertTrue(paths.filter { it.contains("node" / "build") }.isEmpty())
+ }
+}
+
+private fun Path.contains(that: Path): Boolean {
+ val size = that.nameCount
+ (0..nameCount - size).forEach {
+ if (subpath(it, it + size) == that) return true
+ }
+ return false
+}
diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt
index c9d3c65879..0117504c2e 100644
--- a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt
@@ -5,7 +5,7 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.getOrThrow
import net.corda.core.messaging.RPCOps
-import net.corda.core.success
+import net.corda.core.thenMatch
import net.corda.node.services.messaging.getRpcContext
import net.corda.nodeapi.RPCSinceVersion
import net.corda.testing.RPCDriverExposedDSLInterface
@@ -158,12 +158,12 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() {
val clientQuotes = LinkedBlockingQueue()
val clientFuture = proxy.makeComplicatedListenableFuture()
- clientFuture.success {
+ clientFuture.thenMatch({
val name = it.first
- it.second.success {
+ it.second.thenMatch({
clientQuotes += "Quote by $name: $it"
- }
- }
+ }, {})
+ }, {})
assertThat(clientQuotes).isEmpty()
diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt
index 2ffe065832..fb283773d1 100644
--- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt
@@ -4,7 +4,7 @@ import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.core.future
import net.corda.core.messaging.RPCOps
import net.corda.core.millis
-import net.corda.core.random63BitValue
+import net.corda.core.crypto.random63BitValue
import net.corda.core.serialization.CordaSerializable
import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.testing.RPCDriverExposedDSLInterface
diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml
index fa6ff74c5b..c0cb385396 100644
--- a/config/dev/log4j2.xml
+++ b/config/dev/log4j2.xml
@@ -13,7 +13,7 @@
-
+
@@ -27,7 +27,7 @@
fileName="${sys:log-path}/${log-name}.log"
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
-
+
diff --git a/constants.properties b/constants.properties
index 1676023e4a..6993620217 100644
--- a/constants.properties
+++ b/constants.properties
@@ -1,4 +1,4 @@
-gradlePluginsVersion=0.12.4
+gradlePluginsVersion=0.13.2
kotlinVersion=1.1.1
guavaVersion=21.0
bouncycastleVersion=1.57
diff --git a/cordform-common/build.gradle b/cordform-common/build.gradle
index c3c1676b23..340a4b6ec6 100644
--- a/cordform-common/build.gradle
+++ b/cordform-common/build.gradle
@@ -6,6 +6,10 @@ repositories {
mavenCentral()
}
+// This tracks the gradle plugins version and not Corda
+version gradle_plugins_version
+group 'net.corda.plugins'
+
dependencies {
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
diff --git a/cordform-common/src/main/java/net/corda/cordform/CordformNode.java b/cordform-common/src/main/java/net/corda/cordform/CordformNode.java
index 66e0ba9ca0..80a9a3795a 100644
--- a/cordform-common/src/main/java/net/corda/cordform/CordformNode.java
+++ b/cordform-common/src/main/java/net/corda/cordform/CordformNode.java
@@ -7,7 +7,7 @@ import com.typesafe.config.ConfigValueFactory;
import java.util.List;
import java.util.Map;
-public class CordformNode {
+public class CordformNode implements NodeDefinition {
protected static final String DEFAULT_HOST = "localhost";
/**
diff --git a/cordform-common/src/main/java/net/corda/cordform/NodeDefinition.java b/cordform-common/src/main/java/net/corda/cordform/NodeDefinition.java
new file mode 100644
index 0000000000..0b86b98627
--- /dev/null
+++ b/cordform-common/src/main/java/net/corda/cordform/NodeDefinition.java
@@ -0,0 +1,9 @@
+package net.corda.cordform;
+
+import com.typesafe.config.Config;
+
+public interface NodeDefinition {
+ String getName();
+
+ Config getConfig();
+}
diff --git a/core/src/main/kotlin/net/corda/core/utilities/CordaException.kt b/core/src/main/kotlin/net/corda/core/CordaException.kt
similarity index 99%
rename from core/src/main/kotlin/net/corda/core/utilities/CordaException.kt
rename to core/src/main/kotlin/net/corda/core/CordaException.kt
index 907bbee408..49ed6b6975 100644
--- a/core/src/main/kotlin/net/corda/core/utilities/CordaException.kt
+++ b/core/src/main/kotlin/net/corda/core/CordaException.kt
@@ -1,4 +1,4 @@
-package net.corda.core.utilities
+package net.corda.core
import net.corda.core.serialization.CordaSerializable
import java.util.*
diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt
index 2c26c227c2..24d8247df0 100644
--- a/core/src/main/kotlin/net/corda/core/Utils.kt
+++ b/core/src/main/kotlin/net/corda/core/Utils.kt
@@ -7,7 +7,6 @@ import com.google.common.base.Throwables
import com.google.common.io.ByteStreams
import com.google.common.util.concurrent.*
import net.corda.core.crypto.SecureHash
-import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowException
import net.corda.core.serialization.CordaSerializable
@@ -24,9 +23,11 @@ import java.nio.file.*
import java.nio.file.attribute.FileAttribute
import java.time.Duration
import java.time.temporal.Temporal
-import java.util.concurrent.*
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
-import java.util.function.BiConsumer
import java.util.stream.Stream
import java.util.zip.Deflater
import java.util.zip.ZipEntry
@@ -59,12 +60,6 @@ infix fun Int.checkedAdd(b: Int) = Math.addExact(this, b)
@Suppress("unused")
infix fun Long.checkedAdd(b: Long) = Math.addExact(this, b)
-/**
- * Returns a random positive long generated using a secure RNG. This function sacrifies a bit of entropy in order to
- * avoid potential bugs where the value is used in a context where negative numbers are not expected.
- */
-fun random63BitValue(): Long = Math.abs(newSecureRandom().nextLong())
-
/** Same as [Future.get] but with a more descriptive name, and doesn't throw [ExecutionException], instead throwing its cause */
fun Future.getOrThrow(timeout: Duration? = null): T {
return try {
@@ -74,38 +69,20 @@ fun Future.getOrThrow(timeout: Duration? = null): T {
}
}
-fun future(block: () -> T): ListenableFuture = CompletableToListenable(CompletableFuture.supplyAsync(block))
+fun future(block: () -> V): Future = CompletableFuture.supplyAsync(block)
-private class CompletableToListenable(private val base: CompletableFuture) : Future by base, ListenableFuture {
- override fun addListener(listener: Runnable, executor: Executor) {
- base.whenCompleteAsync(BiConsumer { _, _ -> listener.run() }, executor)
- }
-}
+fun , V> F.then(block: (F) -> V) = addListener(Runnable { block(this) }, MoreExecutors.directExecutor())
-// Some utilities for working with Guava listenable futures.
-fun ListenableFuture.then(executor: Executor, body: () -> Unit) = addListener(Runnable(body), executor)
-
-fun ListenableFuture.success(executor: Executor, body: (T) -> Unit) = then(executor) {
- val r = try {
- get()
- } catch(e: Throwable) {
- return@then
- }
- body(r)
-}
-
-fun ListenableFuture.failure(executor: Executor, body: (Throwable) -> Unit) = then(executor) {
- try {
+fun Future.match(success: (U) -> V, failure: (Throwable) -> V): V {
+ return success(try {
getOrThrow()
} catch (t: Throwable) {
- body(t)
- }
+ return failure(t)
+ })
}
-infix fun ListenableFuture.then(body: () -> Unit): ListenableFuture = apply { then(RunOnCallerThread, body) }
-infix fun ListenableFuture.success(body: (T) -> Unit): ListenableFuture = apply { success(RunOnCallerThread, body) }
-infix fun ListenableFuture.failure(body: (Throwable) -> Unit): ListenableFuture = apply { failure(RunOnCallerThread, body) }
-fun ListenableFuture<*>.andForget(log: Logger) = failure(RunOnCallerThread) { log.error("Background task failed:", it) }
+fun ListenableFuture.thenMatch(success: (U) -> V, failure: (Throwable) -> W) = then { it.match(success, failure) }
+fun ListenableFuture<*>.andForget(log: Logger) = then { it.match({}, { log.error("Background task failed:", it) }) }
@Suppress("UNCHECKED_CAST") // We need the awkward cast because otherwise F cannot be nullable, even though it's safe.
infix fun ListenableFuture.map(mapper: (F) -> T): ListenableFuture = Futures.transform(this, { (mapper as (F?) -> T)(it) })
infix fun ListenableFuture.flatMap(mapper: (F) -> ListenableFuture): ListenableFuture = Futures.transformAsync(this) { mapper(it!!) }
@@ -121,12 +98,12 @@ inline fun SettableFuture.catch(block: () -> T) {
fun ListenableFuture.toObservable(): Observable {
return Observable.create { subscriber ->
- success {
+ thenMatch({
subscriber.onNext(it)
subscriber.onCompleted()
- } failure {
+ }, {
subscriber.onError(it)
- }
+ })
}
}
@@ -211,9 +188,6 @@ fun List.randomOrNull(): T? {
/** Returns a random element in the list matching the given predicate, or null if none found */
fun List.randomOrNull(predicate: (T) -> Boolean) = filter(predicate).randomOrNull()
-// An alias that can sometimes make code clearer to read.
-val RunOnCallerThread: Executor = MoreExecutors.directExecutor()
-
inline fun elapsedTime(block: () -> Unit): Duration {
val start = System.nanoTime()
block()
@@ -353,63 +327,6 @@ data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHa
val Throwable.rootCause: Throwable get() = Throwables.getRootCause(this)
-/** Representation of an operation that may have thrown an error. */
-@Suppress("DataClassPrivateConstructor")
-@CordaSerializable
-data class ErrorOr private constructor(val value: A?, val error: Throwable?) {
- // The ErrorOr holds a value iff error == null
- constructor(value: A) : this(value, null)
-
- companion object {
- /** Runs the given lambda and wraps the result. */
- inline fun catch(body: () -> T): ErrorOr {
- return try {
- ErrorOr(body())
- } catch (t: Throwable) {
- ErrorOr.of(t)
- }
- }
-
- fun of(t: Throwable) = ErrorOr(null, t)
- }
-
- fun match(onValue: (A) -> T, onError: (Throwable) -> T): T {
- if (error == null) {
- return onValue(value as A)
- } else {
- return onError(error)
- }
- }
-
- fun getOrThrow(): A {
- if (error == null) {
- return value as A
- } else {
- throw error
- }
- }
-
- // Functor
- fun map(function: (A) -> B) = ErrorOr(value?.let(function), error)
-
- // Applicative
- fun combine(other: ErrorOr, function: (A, B) -> C): ErrorOr {
- val newError = error ?: other.error
- return ErrorOr(if (newError != null) null else function(value as A, other.value as B), newError)
- }
-
- // Monad
- fun bind(function: (A) -> ErrorOr): ErrorOr {
- return if (error == null) {
- function(value as A)
- } else {
- ErrorOr.of(error)
- }
- }
-
- fun mapError(function: (Throwable) -> Throwable) = ErrorOr(value, error?.let(function))
-}
-
/**
* Returns an Observable that buffers events until subscribed.
* @see UnicastSubject
diff --git a/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt
new file mode 100644
index 0000000000..8ab4d6f4e1
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt
@@ -0,0 +1,37 @@
+package net.corda.core.concurrent
+
+import com.google.common.annotations.VisibleForTesting
+import com.google.common.util.concurrent.ListenableFuture
+import com.google.common.util.concurrent.SettableFuture
+import net.corda.core.catch
+import net.corda.core.match
+import net.corda.core.then
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * As soon as a given future becomes done, the handler is invoked with that future as its argument.
+ * The result of the handler is copied into the result future, and the handler isn't invoked again.
+ * If a given future errors after the result future is done, the error is automatically logged.
+ */
+fun firstOf(vararg futures: ListenableFuture, handler: (ListenableFuture) -> T) = firstOf(futures, defaultLog, handler)
+
+private val defaultLog = LoggerFactory.getLogger("net.corda.core.concurrent")
+@VisibleForTesting
+internal val shortCircuitedTaskFailedMessage = "Short-circuited task failed:"
+
+internal fun firstOf(futures: Array>, log: Logger, handler: (ListenableFuture) -> T): ListenableFuture {
+ val resultFuture = SettableFuture.create()
+ val winnerChosen = AtomicBoolean()
+ futures.forEach {
+ it.then {
+ if (winnerChosen.compareAndSet(false, true)) {
+ resultFuture.catch { handler(it) }
+ } else if (!it.isCancelled) {
+ it.match({}, { log.error(shortCircuitedTaskFailedMessage, it) })
+ }
+ }
+ }
+ return resultFuture
+}
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 d10d9cc2bb..0740a4a7e4 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
@@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.serialization.*
+import net.corda.core.utilities.OpaqueBytes
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
@@ -79,8 +80,7 @@ interface ContractState {
* so that they receive the updated state, and don't end up in a situation where they can no longer use a state
* they possess, since someone consumed that state during the notary change process.
*
- * The participants list should normally be derived from the contents of the state. E.g. for [Cash] the participants
- * list should just contain the owner.
+ * The participants list should normally be derived from the contents of the state.
*/
val participants: List
}
@@ -126,7 +126,7 @@ infix fun T.withNotary(newNotary: Party) = TransactionState(
* Definition for an issued product, which can be cash, a cash-like thing, assets, or generally anything else that's
* quantifiable with integer quantities.
*
- * @param P the type of product underlying the definition, for example [Currency].
+ * @param P the type of product underlying the definition, for example [java.util.Currency].
*/
@CordaSerializable
data class Issued(val issuer: PartyAndReference, val product: P) {
@@ -159,8 +159,8 @@ interface Scheduled {
}
/**
- * Represents a contract state (unconsumed output) of type [LinearState] and a point in time that a lifecycle event is expected to take place
- * for that contract state.
+ * Represents a contract state (unconsumed output) of type [LinearState] and a point in time that a lifecycle event is
+ * expected to take place for that contract state.
*
* This is effectively the input to a scheduler, which wakes up at that point in time and asks the contract state what
* lifecycle processing needs to take place. e.g. a fixing or a late payment etc.
@@ -168,10 +168,11 @@ interface Scheduled {
data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instant) : Scheduled
/**
- * This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a given point in time.
- * e.g. run a fixing flow.
+ * This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a
+ * given point in time. e.g. run a fixing flow.
*
- * Note the use of [FlowLogicRef] to represent a safe way to transport a [FlowLogic] out of the contract sandbox.
+ * Note the use of [FlowLogicRef] to represent a safe way to transport a [net.corda.core.flows.FlowLogic] out of the
+ * contract sandbox.
*
* Currently we support only flow based activities as we expect there to be a transaction generated off the back of
* the activity, otherwise we have to start tracking secondary state on the platform of which scheduled activities
@@ -383,9 +384,9 @@ class TimeWindow private constructor(
// DOCSTART 5
/**
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
- * every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the
- * transaction for it to be accepted: failure of any aborts the entire thing. The time is taken from a trusted
- * time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
+ * every [net.corda.core.transactions.LedgerTransaction] they see on the network, for every input and output state. All
+ * contracts must accept the transaction for it to be accepted: failure of any aborts the entire thing. The time is taken
+ * from a trusted time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
*
* TODO: Contract serialization is likely to change, so the annotation is likely temporary.
*/
@@ -461,9 +462,8 @@ interface Attachment : NamedByHash {
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
companion object {
fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
- val storage = serviceHub.storageService.attachments
return {
- val a = storage.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
+ val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
(a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
}
}
diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionGraphSearch.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionGraphSearch.kt
index 4e9bc3e006..4239b5772b 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/TransactionGraphSearch.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionGraphSearch.kt
@@ -1,7 +1,7 @@
package net.corda.core.contracts
import net.corda.core.crypto.SecureHash
-import net.corda.core.node.services.ReadOnlyTransactionStorage
+import net.corda.core.node.services.TransactionStorage
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import java.util.*
@@ -18,7 +18,7 @@ import java.util.concurrent.Callable
* @param transactions map of transaction id to [SignedTransaction].
* @param startPoints transactions to use as starting points for the search.
*/
-class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage,
+class TransactionGraphSearch(val transactions: TransactionStorage,
val startPoints: List) : Callable> {
class Query(
val withCommandOfType: Class? = null,
diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt
index b1e98a49e9..db388b5c8c 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt
@@ -2,7 +2,6 @@ package net.corda.core.contracts
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.DeserializeAsKotlinObjectDef
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
@@ -61,7 +60,7 @@ sealed class TransactionType {
abstract fun verifyTransaction(tx: LedgerTransaction)
/** A general transaction type where transaction validity is determined by custom contract code */
- object General : TransactionType(), DeserializeAsKotlinObjectDef {
+ object General : TransactionType() {
/** Just uses the default [TransactionBuilder] with no special logic */
class Builder(notary: Party?) : TransactionBuilder(General, notary)
@@ -141,15 +140,16 @@ sealed class TransactionType {
* A special transaction type for reassigning a notary for a state. Validation does not involve running
* any contract code, it just checks that the states are unmodified apart from the notary field.
*/
- object NotaryChange : TransactionType(), DeserializeAsKotlinObjectDef {
+ object NotaryChange : TransactionType() {
/**
* A transaction builder that automatically sets the transaction type to [NotaryChange]
* and adds the list of participants to the signers set for every input state.
*/
class Builder(notary: Party) : TransactionBuilder(NotaryChange, notary) {
- override fun addInputState(stateAndRef: StateAndRef<*>) {
+ override fun addInputState(stateAndRef: StateAndRef<*>): TransactionBuilder {
signers.addAll(stateAndRef.state.data.participants.map { it.owningKey })
super.addInputState(stateAndRef)
+ return this
}
}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
deleted file mode 100644
index 1e0ae94678..0000000000
--- a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-package net.corda.core.crypto
-
-import net.corda.core.crypto.CompositeKey.NodeAndWeight
-import net.corda.core.serialization.CordaSerializable
-import org.bouncycastle.asn1.*
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
-import java.security.PublicKey
-
-/**
- * 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.
- *
- * 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.
- * The root node would specify *weights* for each of its children and a *threshold* – the minimum total weight required
- * (e.g. the minimum number of child signatures required) to satisfy the tree signature requirement.
- *
- * 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
- * signatures required) to satisfy the sub-tree rooted at this node.
- */
-@CordaSerializable
-class CompositeKey private constructor (val threshold: Int,
- children: List) : PublicKey {
- val children = children.sorted()
- init {
- require (children.size == children.toSet().size) { "Trying to construct CompositeKey with duplicated child nodes." }
- // If we want PublicKey we only keep one key, otherwise it will lead to semantically equivalent trees but having different structures.
- require(children.size > 1) { "Cannot construct CompositeKey with only one child node." }
- }
-
- /**
- * Holds node - weight pairs for a CompositeKey. Ordered first by weight, then by node's hashCode.
- */
- @CordaSerializable
- data class NodeAndWeight(val node: PublicKey, val weight: Int): Comparable, ASN1Object() {
- override fun compareTo(other: NodeAndWeight): Int {
- if (weight == other.weight) {
- return node.hashCode().compareTo(other.node.hashCode())
- }
- else return weight.compareTo(other.weight)
- }
-
- override fun toASN1Primitive(): ASN1Primitive {
- val vector = ASN1EncodableVector()
- vector.add(DERBitString(node.encoded))
- vector.add(ASN1Integer(weight.toLong()))
- return DERSequence(vector)
- }
- }
-
- companion object {
- val ALGORITHM = CompositeSignature.ALGORITHM_IDENTIFIER.algorithm.toString()
- }
-
- /**
- * Takes single PublicKey and checks if CompositeKey requirements hold for that key.
- */
- fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
-
- override fun getAlgorithm() = ALGORITHM
- override fun getEncoded(): ByteArray {
- val keyVector = ASN1EncodableVector()
- val childrenVector = ASN1EncodableVector()
- children.forEach {
- childrenVector.add(it.toASN1Primitive())
- }
- keyVector.add(ASN1Integer(threshold.toLong()))
- keyVector.add(DERSequence(childrenVector))
- return SubjectPublicKeyInfo(CompositeSignature.ALGORITHM_IDENTIFIER, DERSequence(keyVector)).encoded
- }
- override fun getFormat() = ASN1Encoding.DER
-
- /**
- * Function checks if the public keys corresponding to the signatures are matched against the leaves of the composite
- * key tree in question, and the total combined weight of all children is calculated for every intermediary node.
- * If all thresholds are satisfied, the composite key requirement is considered to be met.
- */
- fun isFulfilledBy(keysToCheck: Iterable): Boolean {
- if (keysToCheck.any { it is CompositeKey } ) return false
- val totalWeight = children.map { (node, weight) ->
- if (node is CompositeKey) {
- if (node.isFulfilledBy(keysToCheck)) weight else 0
- } else {
- if (keysToCheck.contains(node)) weight else 0
- }
- }.sum()
- return totalWeight >= threshold
- }
-
- /**
- * Set of all leaf keys of that CompositeKey.
- */
- val leafKeys: Set
- get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is CompositeKey) return false
- if (threshold != other.threshold) return false
- if (children != other.children) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = threshold
- result = 31 * result + children.hashCode()
- return result
- }
-
- override fun toString() = "(${children.joinToString()})"
-
- /** A helper class for building a [CompositeKey]. */
- class Builder {
- private val children: MutableList = mutableListOf()
-
- /** Adds a child [CompositeKey] node. Specifying a [weight] for the child is optional and will default to 1. */
- fun addKey(key: PublicKey, weight: Int = 1): Builder {
- children.add(NodeAndWeight(key, weight))
- return this
- }
-
- fun addKeys(vararg keys: PublicKey): Builder {
- keys.forEach { addKey(it) }
- return this
- }
-
- fun addKeys(keys: List): Builder = addKeys(*keys.toTypedArray())
-
- /**
- * Builds the [CompositeKey]. If [threshold] is not specified, it will default to
- * the size of the children, effectively generating an "N of N" requirement.
- * During process removes single keys wrapped in [CompositeKey] and enforces ordering on child nodes.
- */
- @Throws(IllegalArgumentException::class)
- fun build(threshold: Int? = null): PublicKey {
- val n = children.size
- if (n > 1)
- return CompositeKey(threshold ?: n, children)
- else if (n == 1) {
- require(threshold == null || threshold == children.first().weight)
- { "Trying to build invalid CompositeKey, threshold value different than weight of single child node." }
- return children.first().node // We can assume that this node is a correct CompositeKey.
- }
- else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
- }
- }
-}
-
-/**
- * Expands all [CompositeKey]s present in PublicKey iterable to set of single [PublicKey]s.
- * If an element of the set is a single PublicKey it gives just that key, if it is a [CompositeKey] it returns all leaf
- * keys for that composite element.
- */
-val Iterable.expandedCompositeKeys: Set
- get() = flatMap { it.keys }.toSet()
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt b/core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt
index ed3222bf18..cf679e8a7d 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt
@@ -14,7 +14,7 @@ import java.security.Signature
*/
object ContentSignerBuilder {
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider?, random: SecureRandom? = null): ContentSigner {
- val sigAlgId = AlgorithmIdentifier(signatureScheme.signatureOID)
+ val sigAlgId = signatureScheme.signatureOID
val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply {
if (random != null) {
initSign(privateKey, random)
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 c508c9a176..abfcaa964e 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -1,24 +1,26 @@
package net.corda.core.crypto
-import net.corda.core.random63BitValue
-import net.i2p.crypto.eddsa.*
+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.i2p.crypto.eddsa.EdDSAEngine
+import net.i2p.crypto.eddsa.EdDSAPrivateKey
+import net.i2p.crypto.eddsa.EdDSAPublicKey
+import net.i2p.crypto.eddsa.EdDSASecurityProvider
import net.i2p.crypto.eddsa.math.GroupElement
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
-import org.bouncycastle.asn1.ASN1EncodableVector
-import org.bouncycastle.asn1.ASN1ObjectIdentifier
-import org.bouncycastle.asn1.ASN1Sequence
-import org.bouncycastle.asn1.DERSequence
+import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.bc.BCObjectIdentifiers
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
+import org.bouncycastle.asn1.sec.SECObjectIdentifiers
import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.asn1.x509.BasicConstraints
-import org.bouncycastle.asn1.x509.Extension
-import org.bouncycastle.asn1.x509.NameConstraints
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
+import org.bouncycastle.asn1.x509.*
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
@@ -45,13 +47,8 @@ import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
-import sun.security.pkcs.PKCS8Key
-import sun.security.util.DerValue
-import sun.security.x509.X509Key
import java.math.BigInteger
import java.security.*
-import java.security.KeyFactory
-import java.security.KeyPairGenerator
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
@@ -80,7 +77,8 @@ object Crypto {
val RSA_SHA256 = SignatureScheme(
1,
"RSA_SHA256",
- PKCSObjectIdentifiers.id_RSASSA_PSS,
+ AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null),
+ emptyList(),
BouncyCastleProvider.PROVIDER_NAME,
"RSA",
"SHA256WITHRSAANDMGF1",
@@ -93,7 +91,8 @@ object Crypto {
val ECDSA_SECP256K1_SHA256 = SignatureScheme(
2,
"ECDSA_SECP256K1_SHA256",
- X9ObjectIdentifiers.ecdsa_with_SHA256,
+ AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256k1),
+ listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256k1)),
BouncyCastleProvider.PROVIDER_NAME,
"ECDSA",
"SHA256withECDSA",
@@ -106,7 +105,8 @@ object Crypto {
val ECDSA_SECP256R1_SHA256 = SignatureScheme(
3,
"ECDSA_SECP256R1_SHA256",
- X9ObjectIdentifiers.ecdsa_with_SHA256,
+ AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256r1),
+ listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256r1)),
BouncyCastleProvider.PROVIDER_NAME,
"ECDSA",
"SHA256withECDSA",
@@ -119,10 +119,12 @@ object Crypto {
val EDDSA_ED25519_SHA512 = SignatureScheme(
4,
"EDDSA_ED25519_SHA512",
- ASN1ObjectIdentifier("1.3.101.112"),
+ // OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00
+ AlgorithmIdentifier(ASN1ObjectIdentifier("1.3.101.112"), null),
+ emptyList(),
// We added EdDSA to bouncy castle for certificate signing.
BouncyCastleProvider.PROVIDER_NAME,
- EdDSAKey.KEY_ALGORITHM,
+ "1.3.101.112",
EdDSAEngine.SIGNATURE_ALGORITHM,
EdDSANamedCurveTable.getByName("ED25519"),
256,
@@ -133,10 +135,12 @@ object Crypto {
* SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers
* at the cost of larger key sizes and loss of compatibility.
*/
+ val SHA512_256 = DLSequence(arrayOf(NISTObjectIdentifiers.id_sha512_256))
val SPHINCS256_SHA256 = SignatureScheme(
5,
"SPHINCS-256_SHA512",
- BCObjectIdentifiers.sphincs256_with_SHA512,
+ AlgorithmIdentifier(BCObjectIdentifiers.sphincs256_with_SHA512, DLSequence(arrayOf(ASN1Integer(0), SHA512_256))),
+ listOf(AlgorithmIdentifier(BCObjectIdentifiers.sphincs256, DLSequence(arrayOf(ASN1Integer(0), SHA512_256)))),
"BCPQC",
"SPHINCS256",
"SHA512WITHSPHINCS256",
@@ -146,6 +150,22 @@ object Crypto {
"at the cost of larger key sizes and loss of compatibility."
)
+ /**
+ * Corda composite key type
+ */
+ val COMPOSITE_KEY = SignatureScheme(
+ 6,
+ "COMPOSITE",
+ AlgorithmIdentifier(CordaObjectIdentifier.compositeKey),
+ emptyList(),
+ CordaSecurityProvider.PROVIDER_NAME,
+ CompositeKey.KEY_ALGORITHM,
+ CompositeSignature.SIGNATURE_ALGORITHM,
+ null,
+ null,
+ "Composite keys composed from individual public keys"
+ )
+
/** Our default signature scheme if no algorithm is specified (e.g. for key generation). */
val DEFAULT_SIGNATURE_SCHEME = EDDSA_ED25519_SHA512
@@ -158,12 +178,18 @@ object Crypto {
ECDSA_SECP256K1_SHA256,
ECDSA_SECP256R1_SHA256,
EDDSA_ED25519_SHA512,
- SPHINCS256_SHA256
+ SPHINCS256_SHA256,
+ COMPOSITE_KEY
).associateBy { it.schemeCodeName }
- // We need to group signature schemes per algorithm, so to quickly identify them during decoding.
- // Please note there are schemes with the same algorithm, e.g. EC (or ECDSA) keys are used for both ECDSA_SECP256K1_SHA256 and ECDSA_SECP256R1_SHA256.
- private val algorithmGroups = supportedSignatureSchemes.values.groupBy { it.algorithmName }
+ /**
+ * Map of X.509 algorithm identifiers to signature schemes Corda recognises. See RFC 2459 for the format of
+ * algorithm identifiers.
+ */
+ private val algorithmMap: Map
+ = (supportedSignatureSchemes.values.flatMap { scheme -> scheme.alternativeOIDs.map { oid -> Pair(oid, scheme) } }
+ + supportedSignatureSchemes.values.map { Pair(it.signatureOID, it) })
+ .toMap()
// This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider
// that could cause unexpected and suspicious behaviour.
@@ -171,17 +197,34 @@ object Crypto {
// The val is private to avoid any harmful state changes.
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.
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
putAll(EdDSASecurityProvider())
- addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID, KeyInfoConverter(EDDSA_ED25519_SHA512))
+ addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID.algorithm, KeyInfoConverter(EDDSA_ED25519_SHA512))
}
init {
// This registration is needed for reading back EdDSA key from java keystore.
// TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider.
Security.addProvider(getBouncyCastleProvider())
+ Security.addProvider(CordaSecurityProvider())
+ }
+
+ /**
+ * Normalise an algorithm identifier by converting [DERNull] parameters into a Kotlin null value.
+ */
+ private fun normaliseAlgorithmIdentifier(id: AlgorithmIdentifier): AlgorithmIdentifier {
+ return if (id.parameters is DERNull) {
+ AlgorithmIdentifier(id.algorithm, null)
+ } else {
+ id
+ }
+ }
+
+ fun findSignatureScheme(algorithm: AlgorithmIdentifier): SignatureScheme {
+ return algorithmMap[normaliseAlgorithmIdentifier(algorithm)] ?: throw IllegalArgumentException("Unrecognised algorithm: ${algorithm.algorithm.id}")
}
/**
@@ -192,6 +235,7 @@ object Crypto {
* @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested signature scheme is not supported.
*/
+ @Throws(IllegalArgumentException::class)
fun findSignatureScheme(schemeCodeName: String): SignatureScheme = supportedSignatureSchemes[schemeCodeName] ?: throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $schemeCodeName")
/**
@@ -202,10 +246,24 @@ object Crypto {
* @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested key type is not supported.
*/
- fun findSignatureScheme(key: Key): SignatureScheme {
- val algorithm = matchingAlgorithmName(key.algorithm)
- algorithmGroups[algorithm]?.filter { validateKey(it, key) }?.firstOrNull { return it }
- throw IllegalArgumentException("Unsupported key algorithm: ${key.algorithm} or invalid key format")
+ @Throws(IllegalArgumentException::class)
+ fun findSignatureScheme(key: PublicKey): SignatureScheme {
+ val keyInfo = SubjectPublicKeyInfo.getInstance(key.encoded)
+ return findSignatureScheme(keyInfo.algorithm)
+ }
+
+ /**
+ * Retrieve the corresponding [SignatureScheme] based on the type of the input [Key].
+ * This function is usually called when requiring to verify signatures and the signing schemes must be defined.
+ * For the supported signature schemes see [Crypto].
+ * @param key either private or public.
+ * @return a currently supported SignatureScheme.
+ * @throws IllegalArgumentException if the requested key type is not supported.
+ */
+ @Throws(IllegalArgumentException::class)
+ fun findSignatureScheme(key: PrivateKey): SignatureScheme {
+ val keyInfo = PrivateKeyInfo.getInstance(key.encoded)
+ return findSignatureScheme(keyInfo.privateKeyAlgorithm)
}
/**
@@ -217,19 +275,9 @@ object Crypto {
*/
@Throws(IllegalArgumentException::class)
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
- val algorithm = matchingAlgorithmName(PKCS8Key.parseKey(DerValue(encodedKey)).algorithm)
- // There are cases where the same key algorithm is applied to different signature schemes.
- // Currently, this occurs with ECDSA as it applies to either secp256K1 or secp256R1 curves.
- // In such a case, we should try and identify which of the candidate schemes is the correct one so as
- // to generate the appropriate key.
- for (signatureScheme in algorithmGroups[algorithm]!!) {
- try {
- return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
- } catch (ikse: InvalidKeySpecException) {
- // ignore it - only used to bypass the scheme that causes an exception, as it has the same name, but different params.
- }
- }
- throw IllegalArgumentException("This private key cannot be decoded, please ensure it is PKCS8 encoded and the signature scheme is supported.")
+ val keyInfo = PrivateKeyInfo.getInstance(encodedKey)
+ val signatureScheme = findSignatureScheme(keyInfo.privateKeyAlgorithm)
+ return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
}
/**
@@ -270,19 +318,9 @@ object Crypto {
*/
@Throws(IllegalArgumentException::class)
fun decodePublicKey(encodedKey: ByteArray): PublicKey {
- val algorithm = matchingAlgorithmName(X509Key.parse(DerValue(encodedKey)).algorithm)
- // There are cases where the same key algorithm is applied to different signature schemes.
- // Currently, this occurs with ECDSA as it applies to either secp256K1 or secp256R1 curves.
- // In such a case, we should try and identify which of the candidate schemes is the correct one so as
- // to generate the appropriate key.
- for (signatureScheme in algorithmGroups[algorithm]!!) {
- try {
- return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
- } catch (ikse: InvalidKeySpecException) {
- // ignore it - only used to bypass the scheme that causes an exception, as it has the same name, but different params.
- }
- }
- throw IllegalArgumentException("This public key cannot be decoded, please ensure it is X509 encoded and the signature scheme is supported.")
+ val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(encodedKey)
+ val signatureScheme = findSignatureScheme(subjectPublicKeyInfo.algorithm)
+ return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
}
/**
@@ -527,7 +565,7 @@ object Crypto {
if (signatureScheme.algSpec != null)
keyPairGenerator.initialize(signatureScheme.algSpec, newSecureRandom())
else
- keyPairGenerator.initialize(signatureScheme.keySize, newSecureRandom())
+ keyPairGenerator.initialize(signatureScheme.keySize!!, newSecureRandom())
return keyPairGenerator.generateKeyPair()
}
@@ -834,16 +872,6 @@ object Crypto {
/** Check if the requested [SignatureScheme] is supported by the system. */
fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean = supportedSignatureSchemes[signatureScheme.schemeCodeName] === signatureScheme
- // map algorithm names returned from Keystore (or after encode/decode) to the supported algorithm names.
- private fun matchingAlgorithmName(algorithm: String): String {
- return when (algorithm) {
- "EC" -> "ECDSA"
- "SPHINCS-256" -> "SPHINCS256"
- "1.3.6.1.4.1.22554.2.1" -> "SPHINCS256" // Unfortunately, PKCS8Key and X509Key parsing return the OID as the algorithm name and not SPHINCS256.
- else -> algorithm
- }
- }
-
// validate a key, by checking its algorithmic params.
private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean {
return when (key) {
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 28d4e18f4a..c77131522a 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
@@ -2,42 +2,12 @@
package net.corda.core.crypto
-import net.corda.core.identity.AnonymousParty
-import net.corda.core.identity.Party
-import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.utilities.OpaqueBytes
import java.math.BigInteger
import net.corda.core.utilities.SgxSupport
import java.security.*
-@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)
-
-// TODO: Clean up this duplication between Null and Dummy public key
-@CordaSerializable
-@Deprecated("Has encoding format problems, consider entropyToKeyPair() instead")
-class DummyPublicKey(val s: String) : PublicKey, Comparable {
- override fun getAlgorithm() = "DUMMY"
- override fun getEncoded() = s.toByteArray()
- override fun getFormat() = "ASN.1"
- override fun compareTo(other: PublicKey): Int = BigInteger(encoded).compareTo(BigInteger(other.encoded))
- override fun equals(other: Any?) = other is DummyPublicKey && other.s == s
- override fun hashCode(): Int = s.hashCode()
- override fun toString() = "PUBKEY[$s]"
-}
-
-/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
-@CordaSerializable
-object NullSignature : DigitalSignature.WithKey(NullPublicKey, ByteArray(32))
-
/**
* Utility to simplify the act of signing a byte array.
* @param bytesToSign the data/message to be signed in [ByteArray] form (usually the Merkle root).
@@ -66,17 +36,6 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignat
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public)
fun KeyPair.sign(bytesToSign: OpaqueBytes) = private.sign(bytesToSign.bytes, public)
-fun KeyPair.sign(bytesToSign: OpaqueBytes, party: Party) = sign(bytesToSign.bytes, party)
-
-// TODO This case will need more careful thinking, as party owningKey can be a CompositeKey. One way of doing that is
-// implementation of CompositeSignature.
-@Throws(InvalidKeyException::class)
-fun KeyPair.sign(bytesToSign: ByteArray, party: Party): DigitalSignature.LegallyIdentifiable {
- // Quick workaround when we have CompositeKey as Party owningKey.
- if (party.owningKey is CompositeKey) throw InvalidKeyException("Signing for parties with CompositeKey not supported.")
- val sig = sign(bytesToSign)
- return DigitalSignature.LegallyIdentifiable(party, sig.bytes)
-}
/**
* Utility to simplify the act of verifying a signature.
@@ -262,3 +221,17 @@ private val _newSecureRandom: () -> SecureRandom by lazy {
*/
@Throws(NoSuchAlgorithmException::class)
fun newSecureRandom() = _newSecureRandom()
+
+/**
+ * Returns a random positive non-zero long generated using a secure RNG. This function sacrifies a bit of entropy in order
+ * to avoid potential bugs where the value is used in a context where negative numbers or zero are not expected.
+ */
+fun random63BitValue(): Long {
+ while (true) {
+ val candidate = Math.abs(newSecureRandom().nextLong())
+ // No need to check for -0L
+ if (candidate != 0L && candidate != Long.MIN_VALUE) {
+ return candidate
+ }
+ }
+}
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 01c0a0d2be..db7cf6473a 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
@@ -1,8 +1,7 @@
package net.corda.core.crypto
-import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import java.security.InvalidKeyException
import java.security.PublicKey
import java.security.SignatureException
@@ -46,7 +45,4 @@ open class DigitalSignature(bits: ByteArray) : OpaqueBytes(bits) {
@Throws(InvalidKeyException::class, SignatureException::class)
fun isValid(content: ByteArray) = by.isValid(content, this)
}
-
- // TODO: consider removing this as whoever needs to identify the signer should be able to derive it from the public key
- class LegallyIdentifiable(val signer: Party, bits: ByteArray) : WithKey(signer.owningKey, bits)
}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt
index b1681a19f9..a79821b760 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt
@@ -65,4 +65,4 @@ fun String.hexToBase64(): String = hexToByteArray().toBase64()
// structure, e.g. mapping a PublicKey to a condition with the specific feature (ED25519).
fun parsePublicKeyBase58(base58String: String): PublicKey = base58String.base58ToByteArray().deserialize()
fun PublicKey.toBase58String(): String = this.serialize().bytes.toBase58()
-fun PublicKey.toSHA256Bytes(): ByteArray = this.serialize().bytes.sha256().bytes
+fun PublicKey.toSHA256Bytes(): ByteArray = this.serialize().bytes.sha256().bytes // TODO: decide on the format of hashed key (encoded Vs serialised).
diff --git a/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt b/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt
index a8dc49ae8e..edcf018e82 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt
@@ -1,7 +1,7 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.opaque
+import net.corda.core.utilities.opaque
import net.corda.core.serialization.serialize
import java.security.PublicKey
import java.time.Instant
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 ee8f4a5afe..fcefe20a5b 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
@@ -2,7 +2,7 @@ package net.corda.core.crypto
import com.google.common.io.BaseEncoding
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import java.security.MessageDigest
/**
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 8f61f1b66d..49493f6d6f 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
@@ -1,6 +1,6 @@
package net.corda.core.crypto
-import org.bouncycastle.asn1.ASN1ObjectIdentifier
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import java.security.Signature
import java.security.spec.AlgorithmParameterSpec
@@ -8,7 +8,9 @@ import java.security.spec.AlgorithmParameterSpec
* This class is used to define a digital signature scheme.
* @param schemeNumberID we assign a number ID for more efficient on-wire serialisation. Please ensure uniqueness between schemes.
* @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
- * @param signatureOID object identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA)
+ * @param signatureOID ASN.1 algorithm identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA)
+ * @param alternativeOIDs ASN.1 algorithm identifiers for keys of the signature, where we want to map multiple keys to
+ * the same signature scheme.
* @param providerName the provider's name (e.g. "BC").
* @param algorithmName which signature algorithm is used (e.g. RSA, ECDSA. EdDSA, SPHINCS-256).
* @param signatureName a signature-scheme name as required to create [Signature] objects (e.g. "SHA256withECDSA")
@@ -20,10 +22,11 @@ import java.security.spec.AlgorithmParameterSpec
data class SignatureScheme(
val schemeNumberID: Int,
val schemeCodeName: String,
- val signatureOID: ASN1ObjectIdentifier,
+ val signatureOID: AlgorithmIdentifier,
+ val alternativeOIDs: List,
val providerName: String,
val algorithmName: String,
val signatureName: String,
val algSpec: AlgorithmParameterSpec?,
- val keySize: Int,
+ val keySize: Int?,
val desc: String)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt
new file mode 100644
index 0000000000..51a13a076a
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt
@@ -0,0 +1,277 @@
+package net.corda.core.crypto.composite
+
+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.toSHA256Bytes
+import net.corda.core.crypto.toStringShort
+import net.corda.core.serialization.CordaSerializable
+import org.bouncycastle.asn1.*
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
+import java.nio.ByteBuffer
+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.
+ *
+ * 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.
+ * The root node would specify *weights* for each of its children and a *threshold* – the minimum total weight required
+ * (e.g. the minimum number of child signatures required) to satisfy the tree signature requirement.
+ *
+ * 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
+ * signatures required) to satisfy the sub-tree rooted at this node.
+ */
+@CordaSerializable
+class CompositeKey private constructor(val threshold: Int, children: List) : PublicKey {
+ companion object {
+ val KEY_ALGORITHM = "COMPOSITE"
+ /**
+ * Build a composite key from a DER encoded form.
+ */
+ fun getInstance(encoded: ByteArray) = getInstance(ASN1Primitive.fromByteArray(encoded))
+
+ fun getInstance(asn1: ASN1Primitive): PublicKey {
+ val keyInfo = SubjectPublicKeyInfo.getInstance(asn1)
+ require(keyInfo.algorithm.algorithm == CordaObjectIdentifier.compositeKey)
+ val sequence = ASN1Sequence.getInstance(keyInfo.parsePublicKey())
+ val threshold = ASN1Integer.getInstance(sequence.getObjectAt(0)).positiveValue.toInt()
+ val sequenceOfChildren = ASN1Sequence.getInstance(sequence.getObjectAt(1))
+ val builder = Builder()
+ val listOfChildren = sequenceOfChildren.objects.toList()
+ listOfChildren.forEach { childAsn1 ->
+ require(childAsn1 is ASN1Sequence)
+ val childSeq = childAsn1 as ASN1Sequence
+ val key = Crypto.decodePublicKey((childSeq.getObjectAt(0) as DERBitString).bytes)
+ val weight = ASN1Integer.getInstance(childSeq.getObjectAt(1))
+ builder.addKey(key, weight.positiveValue.toInt())
+ }
+ return builder.build(threshold)
+ }
+ }
+
+ val children = children.sorted()
+
+ init {
+ // TODO: replace with the more extensive, but slower, checkValidity() test.
+ checkConstraints()
+ }
+
+ @Transient
+ private var validated = false
+
+ // Check for key duplication, threshold and weight constraints and test for aggregated weight integer overflow.
+ private fun checkConstraints() {
+ require(children.size == children.toSet().size) { "CompositeKey with duplicated child nodes detected." }
+ // If we want PublicKey we only keep one key, otherwise it will lead to semantically equivalent trees
+ // but having different structures.
+ require(children.size > 1) { "CompositeKey must consist of two or more child nodes." }
+ // We should ensure threshold is positive, because smaller allowable weight for a node key is 1.
+ require(threshold > 0) { "CompositeKey threshold is set to $threshold, but it should be a positive integer." }
+ // If threshold is bigger than total weight, then it will never be satisfied.
+ val totalWeight = totalWeight()
+ require(threshold <= totalWeight) {
+ "CompositeKey threshold: $threshold cannot be bigger than aggregated weight of child nodes: $totalWeight"
+ }
+ }
+
+ // Graph cycle detection in the composite key structure to avoid infinite loops on CompositeKey graph traversal and
+ // when recursion is used (i.e. in isFulfilledBy()).
+ // An IdentityHashMap Vs HashMap is used, because a graph cycle causes infinite loop on the CompositeKey.hashCode().
+ private fun cycleDetection(visitedMap: IdentityHashMap) {
+ for ((node) in children) {
+ if (node is CompositeKey) {
+ val curVisitedMap = IdentityHashMap()
+ curVisitedMap.putAll(visitedMap)
+ require(!curVisitedMap.contains(node)) { "Cycle detected for CompositeKey: $node" }
+ curVisitedMap.put(node, true)
+ node.cycleDetection(curVisitedMap)
+ }
+ }
+ }
+
+ /**
+ * This method will detect graph cycles in the full composite key structure to protect against infinite loops when
+ * traversing the graph and key duplicates in the each layer. It also checks if the threshold and weight constraint
+ * requirements are met, while it tests for aggregated-weight integer overflow.
+ * In practice, this method should be always invoked on the root [CompositeKey], as it inherently
+ * validates the child nodes (all the way till the leaves).
+ * TODO: Always call this method when deserialising [CompositeKey]s.
+ */
+ fun checkValidity() {
+ val visitedMap = IdentityHashMap()
+ visitedMap.put(this, true)
+ cycleDetection(visitedMap) // Graph cycle testing on the root node.
+ checkConstraints()
+ for ((node, _) in children) {
+ if (node is CompositeKey) {
+ // We don't need to check for cycles on the rest of the nodes (testing on the root node is enough).
+ node.checkConstraints()
+ }
+ }
+ validated = true
+ }
+
+ // Method to check if the total (aggregated) weight of child nodes overflows.
+ // Unlike similar solutions that use long conversion, this approach takes advantage of the minimum weight being 1.
+ private fun totalWeight(): Int {
+ var sum = 0
+ for ((_, weight) in children) {
+ require(weight > 0) { "Non-positive weight: $weight detected." }
+ sum = Math.addExact(sum, weight) // Add and check for integer overflow.
+ }
+ return sum
+ }
+
+ /**
+ * Holds node - weight pairs for a CompositeKey. Ordered first by weight, then by node's hashCode.
+ * Each node should be assigned with a positive weight to avoid certain types of weight underflow attacks.
+ */
+ @CordaSerializable
+ data class NodeAndWeight(val node: PublicKey, val weight: Int) : Comparable, ASN1Object() {
+ init {
+ // We don't allow zero or negative weights. Minimum weight = 1.
+ require(weight > 0) { "A non-positive weight was detected. Node info: $this" }
+ }
+
+ override fun compareTo(other: NodeAndWeight): Int {
+ return if (weight == other.weight)
+ ByteBuffer.wrap(node.toSHA256Bytes()).compareTo(ByteBuffer.wrap(other.node.toSHA256Bytes()))
+ else
+ weight.compareTo(other.weight)
+ }
+
+ override fun toASN1Primitive(): ASN1Primitive {
+ val vector = ASN1EncodableVector()
+ vector.add(DERBitString(node.encoded))
+ vector.add(ASN1Integer(weight.toLong()))
+ return DERSequence(vector)
+ }
+
+ override fun toString(): String {
+ return "Public key: ${node.toStringShort()}, weight: $weight"
+ }
+ }
+
+ /**
+ * Takes single PublicKey and checks if CompositeKey requirements hold for that key.
+ */
+ fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
+
+ override fun getAlgorithm() = KEY_ALGORITHM
+
+ override fun getEncoded(): ByteArray {
+ val keyVector = ASN1EncodableVector()
+ val childrenVector = ASN1EncodableVector()
+ children.forEach {
+ childrenVector.add(it.toASN1Primitive())
+ }
+ keyVector.add(ASN1Integer(threshold.toLong()))
+ keyVector.add(DERSequence(childrenVector))
+ return SubjectPublicKeyInfo(AlgorithmIdentifier(CordaObjectIdentifier.compositeKey), DERSequence(keyVector)).encoded
+ }
+
+ override fun getFormat() = ASN1Encoding.DER
+
+ // Extracted method from isFulfilledBy.
+ private fun checkFulfilledBy(keysToCheck: Iterable): Boolean {
+ if (keysToCheck.any { it is CompositeKey }) return false
+ val totalWeight = children.map { (node, weight) ->
+ if (node is CompositeKey) {
+ if (node.checkFulfilledBy(keysToCheck)) weight else 0
+ } else {
+ if (keysToCheck.contains(node)) weight else 0
+ }
+ }.sum()
+ return totalWeight >= threshold
+ }
+
+ /**
+ * Function checks if the public keys corresponding to the signatures are matched against the leaves of the composite
+ * key tree in question, and the total combined weight of all children is calculated for every intermediary node.
+ * If all thresholds are satisfied, the composite key requirement is considered to be met.
+ */
+ fun isFulfilledBy(keysToCheck: Iterable): Boolean {
+ // We validate keys only when checking if they're matched, as this checks subkeys as a result.
+ // Doing these checks at deserialization/construction time would result in duplicate checks.
+ if (!validated)
+ checkValidity() // TODO: remove when checkValidity() will be eventually invoked during/after deserialization.
+ return checkFulfilledBy(keysToCheck)
+ }
+
+ /**
+ * Set of all leaf keys of that CompositeKey.
+ */
+ val leafKeys: Set
+ get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is CompositeKey) return false
+ if (threshold != other.threshold) return false
+ if (children != other.children) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = threshold
+ result = 31 * result + children.hashCode()
+ return result
+ }
+
+ override fun toString() = "(${children.joinToString()})"
+
+ /** A helper class for building a [CompositeKey]. */
+ class Builder {
+ private val children: MutableList = mutableListOf()
+
+ /** Adds a child [CompositeKey] node. Specifying a [weight] for the child is optional and will default to 1. */
+ fun addKey(key: PublicKey, weight: Int = 1): Builder {
+ children.add(NodeAndWeight(key, weight))
+ return this
+ }
+
+ fun addKeys(vararg keys: PublicKey): Builder {
+ keys.forEach { addKey(it) }
+ return this
+ }
+
+ fun addKeys(keys: List): Builder = addKeys(*keys.toTypedArray())
+
+ /**
+ * Builds the [CompositeKey]. If [threshold] is not specified, it will default to
+ * the total (aggregated) weight of the children, effectively generating an "N of N" requirement.
+ * During process removes single keys wrapped in [CompositeKey] and enforces ordering on child nodes.
+ *
+ * @throws IllegalArgumentException
+ */
+ fun build(threshold: Int? = null): PublicKey {
+ val n = children.size
+ return if (n > 1)
+ CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children)
+ else if (n == 1) {
+ require(threshold == null || threshold == children.first().weight)
+ { "Trying to build invalid CompositeKey, threshold value different than weight of single child node." }
+ children.first().node // We can assume that this node is a correct CompositeKey.
+ } else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
+ }
+ }
+}
+
+/**
+ * Expands all [CompositeKey]s present in PublicKey iterable to set of single [PublicKey]s.
+ * If an element of the set is a single PublicKey it gives just that key, if it is a [CompositeKey] it returns all leaf
+ * keys for that composite element.
+ */
+val Iterable.expandedCompositeKeys: Set
+ get() = flatMap { it.keys }.toSet()
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt
similarity index 87%
rename from core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
rename to core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt
index 328af22603..099875b39c 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt
@@ -1,4 +1,4 @@
-package net.corda.core.crypto
+package net.corda.core.crypto.composite
import net.corda.core.serialization.deserialize
import org.bouncycastle.asn1.ASN1ObjectIdentifier
@@ -10,14 +10,10 @@ import java.security.spec.AlgorithmParameterSpec
/**
* Dedicated class for storing a set of signatures that comprise [CompositeKey].
*/
-class CompositeSignature : Signature(ALGORITHM) {
+class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
companion object {
- val ALGORITHM = "2.25.30086077608615255153862931087626791003"
- // UUID-based OID
- // TODO: Register for an OID space and issue our own shorter OID
- val ALGORITHM_IDENTIFIER = AlgorithmIdentifier(ASN1ObjectIdentifier(ALGORITHM))
-
- fun getService(provider: Provider) = Provider.Service(provider, "Signature", ALGORITHM, CompositeSignature::class.java.name, emptyList(), emptyMap())
+ val SIGNATURE_ALGORITHM = "COMPOSITESIG"
+ fun getService(provider: Provider) = Provider.Service(provider, "Signature", SIGNATURE_ALGORITHM, CompositeSignature::class.java.name, emptyList(), emptyMap())
}
private var signatureState: State? = null
diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignaturesWithKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignaturesWithKeys.kt
similarity index 83%
rename from core/src/main/kotlin/net/corda/core/crypto/CompositeSignaturesWithKeys.kt
rename to core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignaturesWithKeys.kt
index 6edac6ce43..5a69484ffa 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignaturesWithKeys.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignaturesWithKeys.kt
@@ -1,5 +1,6 @@
-package net.corda.core.crypto
+package net.corda.core.crypto.composite
+import net.corda.core.crypto.DigitalSignature
import net.corda.core.serialization.CordaSerializable
/**
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt b/core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt
new file mode 100644
index 0000000000..b933188d88
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt
@@ -0,0 +1,34 @@
+package net.corda.core.crypto.composite
+
+import java.security.*
+import java.security.spec.InvalidKeySpecException
+import java.security.spec.KeySpec
+import java.security.spec.X509EncodedKeySpec
+
+class KeyFactory : KeyFactorySpi() {
+
+ @Throws(InvalidKeySpecException::class)
+ override fun engineGeneratePrivate(keySpec: KeySpec): PrivateKey {
+ // Private composite key not supported.
+ throw InvalidKeySpecException("key spec not recognised: " + keySpec.javaClass)
+ }
+
+ @Throws(InvalidKeySpecException::class)
+ override fun engineGeneratePublic(keySpec: KeySpec): PublicKey? {
+ return when (keySpec) {
+ is X509EncodedKeySpec -> CompositeKey.getInstance(keySpec.encoded)
+ else -> throw InvalidKeySpecException("key spec not recognised: " + keySpec.javaClass)
+ }
+ }
+
+ @Throws(InvalidKeySpecException::class)
+ override fun engineGetKeySpec(key: Key, keySpec: Class): T {
+ // Only support [X509EncodedKeySpec].
+ throw InvalidKeySpecException("Not implemented yet $key $keySpec")
+ }
+
+ @Throws(InvalidKeyException::class)
+ override fun engineTranslateKey(key: Key): Key {
+ throw InvalidKeyException("No other composite key providers known")
+ }
+}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt
new file mode 100644
index 0000000000..7951142336
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt
@@ -0,0 +1,37 @@
+package net.corda.core.crypto.provider
+
+import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.crypto.composite.CompositeSignature
+import org.bouncycastle.asn1.ASN1ObjectIdentifier
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier
+import java.security.AccessController
+import java.security.PrivilegedAction
+import java.security.Provider
+
+class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") {
+ companion object {
+ val PROVIDER_NAME = "Corda"
+ }
+
+ init {
+ AccessController.doPrivileged(PrivilegedAction { setup() })
+ }
+
+ 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")
+
+ val compositeKeyOID = CordaObjectIdentifier.compositeKey.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)
+ }
+}
+
+object CordaObjectIdentifier {
+ // UUID-based OID
+ // TODO: Register for an OID space and issue our own shorter OID
+ val compositeKey = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
+ val compositeSignature = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
+}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/testing/DummyKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/testing/DummyKeys.kt
new file mode 100644
index 0000000000..8b699ef38d
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/crypto/testing/DummyKeys.kt
@@ -0,0 +1,35 @@
+package net.corda.core.crypto.testing
+
+import net.corda.core.crypto.DigitalSignature
+import net.corda.core.identity.AnonymousParty
+import net.corda.core.serialization.CordaSerializable
+import java.math.BigInteger
+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)
+
+// TODO: Clean up this duplication between Null and Dummy public key
+@CordaSerializable
+@Deprecated("Has encoding format problems, consider entropyToKeyPair() instead")
+class DummyPublicKey(val s: String) : PublicKey, Comparable {
+ override fun getAlgorithm() = "DUMMY"
+ override fun getEncoded() = s.toByteArray()
+ override fun getFormat() = "ASN.1"
+ override fun compareTo(other: PublicKey): Int = BigInteger(encoded).compareTo(BigInteger(other.encoded))
+ override fun equals(other: Any?) = other is DummyPublicKey && other.s == s
+ override fun hashCode(): Int = s.hashCode()
+ override fun toString() = "PUBKEY[$s]"
+}
+
+/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
+@CordaSerializable
+object NullSignature : DigitalSignature.WithKey(NullPublicKey, ByteArray(32))
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowException.kt b/core/src/main/kotlin/net/corda/core/flows/FlowException.kt
index 153baeae46..e527f22c55 100644
--- a/core/src/main/kotlin/net/corda/core/flows/FlowException.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/FlowException.kt
@@ -1,7 +1,7 @@
package net.corda.core.flows
-import net.corda.core.utilities.CordaException
-import net.corda.core.utilities.CordaRuntimeException
+import net.corda.core.CordaException
+import net.corda.core.CordaRuntimeException
// DOCSTART 1
/**
diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
index a73cda8427..4583d14a62 100644
--- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
@@ -4,13 +4,13 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
+import net.corda.core.messaging.DataFeed
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.debug
import org.slf4j.Logger
-import rx.Observable
/**
* A sub-class of [FlowLogic] implements a flow using direct, straight line blocking code. Thus you
@@ -180,7 +180,7 @@ abstract class FlowLogic {
* @param extraAuditData in the audit log for this permission check these extra key value pairs will be recorded.
*/
@Throws(FlowException::class)
- fun checkFlowPermission(permissionName: String, extraAuditData: Map) = stateMachine.checkFlowPermission(permissionName, extraAuditData)
+ fun checkFlowPermission(permissionName: String, extraAuditData: Map) = stateMachine.checkFlowPermission(permissionName, extraAuditData)
/**
@@ -189,7 +189,7 @@ abstract class FlowLogic {
* @param comment a general human readable summary of the event.
* @param extraAuditData in the audit log for this permission check these extra key value pairs will be recorded.
*/
- fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map) = stateMachine.recordAuditEvent(eventType, comment, extraAuditData)
+ fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map) = stateMachine.recordAuditEvent(eventType, comment, extraAuditData)
/**
* Override this to provide a [ProgressTracker]. If one is provided and stepped, the framework will do something
@@ -215,10 +215,10 @@ abstract class FlowLogic {
*
* @return Returns null if this flow has no progress tracker.
*/
- fun track(): Pair>? {
+ fun track(): DataFeed? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
- it.currentStep.label to it.changes.map { it.toString() }
+ DataFeed(it.currentStep.label, it.changes.map { it.toString() })
}
}
@@ -230,7 +230,7 @@ abstract class FlowLogic {
@Suspendable
fun waitForLedgerCommit(hash: SecureHash): SignedTransaction = stateMachine.waitForLedgerCommit(hash, this)
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////
private var _stateMachine: FlowStateMachine<*>? = null
/**
diff --git a/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt b/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt
index 25f2433ea0..a5f9c709a2 100644
--- a/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt
@@ -4,8 +4,10 @@ import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass
/**
- * This annotation is required by any [FlowLogic] that is designed to be initiated by a counterparty flow. The flow that
- * does the initiating is specified by the [value] property and itself must be annotated with [InitiatingFlow].
+ * This annotation is required by any [FlowLogic] that is designed to be initiated by a counterparty flow. The class must
+ * have at least a constructor which takes in a single [net.corda.core.identity.Party] parameter which represents the
+ * initiating counterparty. The [FlowLogic] that does the initiating is specified by the [value] property and itself must be annotated
+ * with [InitiatingFlow].
*
* The node on startup scans for [FlowLogic]s which are annotated with this and automatically registers the initiating
* to initiated flow mapping.
diff --git a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt
index 5c81e0c4b2..7dc89ae4a5 100644
--- a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt
+++ b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt
@@ -2,7 +2,7 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
diff --git a/core/src/main/kotlin/net/corda/core/identity/AnonymisedIdentity.kt b/core/src/main/kotlin/net/corda/core/identity/AnonymisedIdentity.kt
new file mode 100644
index 0000000000..0048917443
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/identity/AnonymisedIdentity.kt
@@ -0,0 +1,16 @@
+package net.corda.flows
+
+import net.corda.core.identity.AnonymousParty
+import net.corda.core.serialization.CordaSerializable
+import org.bouncycastle.cert.X509CertificateHolder
+import java.security.PublicKey
+import java.security.cert.CertPath
+
+@CordaSerializable
+data class AnonymisedIdentity(
+ val certPath: CertPath,
+ val certificate: X509CertificateHolder,
+ val identity: AnonymousParty) {
+ constructor(certPath: CertPath, certificate: X509CertificateHolder, identity: PublicKey)
+ : this(certPath, certificate, AnonymousParty(identity))
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt b/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt
index dc1ec16f58..33ffffb19b 100644
--- a/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt
+++ b/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt
@@ -2,7 +2,7 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toBase58String
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
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 7bc6ebab8b..e41c550c84 100644
--- a/core/src/main/kotlin/net/corda/core/identity/Party.kt
+++ b/core/src/main/kotlin/net/corda/core/identity/Party.kt
@@ -2,9 +2,7 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.CertificateAndKeyPair
-import net.corda.core.crypto.toBase58String
-import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.OpaqueBytes
+import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
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 bfe9910de8..bf99ebe570 100644
--- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
+++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
@@ -1,7 +1,6 @@
package net.corda.core.messaging
import com.google.common.util.concurrent.ListenableFuture
-import net.corda.core.ErrorOr
import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
@@ -10,16 +9,19 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
+import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
-import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
+import net.corda.core.node.services.VaultQueryException
+import net.corda.core.node.services.vault.DEFAULT_PAGE_SIZE
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
+import net.corda.core.utilities.Try
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable
import java.io.InputStream
@@ -32,7 +34,7 @@ data class StateMachineInfo(
val id: StateMachineRunId,
val flowLogicClassName: String,
val initiator: FlowInitiator,
- val progressTrackerStepAndUpdates: Pair>?
+ val progressTrackerStepAndUpdates: DataFeed?
) {
override fun toString(): String = "${javaClass.simpleName}($id, $flowLogicClassName)"
}
@@ -45,16 +47,16 @@ sealed class StateMachineUpdate {
override val id: StateMachineRunId get() = stateMachineInfo.id
}
- data class Removed(override val id: StateMachineRunId, val result: ErrorOr<*>) : StateMachineUpdate()
+ data class Removed(override val id: StateMachineRunId, val result: Try<*>) : StateMachineUpdate()
}
+@CordaSerializable
+data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash)
+
/**
* RPC operations that the node exposes to clients using the Java client library. These can be called from
* client apps and are implemented by the node in the [net.corda.node.internal.CordaRPCOpsImpl] class.
*/
-
-// TODO: The use of Pairs throughout is unfriendly for Java interop.
-
interface CordaRPCOps : RPCOps {
/**
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
@@ -63,10 +65,13 @@ interface CordaRPCOps : RPCOps {
override val protocolVersion: Int get() = nodeIdentity().platformVersion
/**
- * Returns a pair of currently in-progress state machine infos and an observable of future state machine adds/removes.
+ * Returns a data feed of currently in-progress state machine infos and an observable of future state machine adds/removes.
*/
@RPCReturnsObservables
- fun stateMachinesAndUpdates(): Pair, Observable>
+ fun stateMachinesFeed(): DataFeed, StateMachineUpdate>
+
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("stateMachinesFeed()"))
+ fun stateMachinesAndUpdates() = stateMachinesFeed()
/**
* Returns a snapshot of vault states for a given query criteria (and optional order and paging specification)
@@ -76,11 +81,18 @@ interface CordaRPCOps : RPCOps {
* and returns a [Vault.Page] object containing the following:
* 1. states as a List of (page number and size defined by [PageSpecification])
* 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table.
- * 3. the [PageSpecification] used in the query
- * 4. a total number of results available (for subsequent paging if necessary)
+ * 3. total number of results available if [PageSpecification] supplied (otherwise returns -1)
+ * 4. status types used in this query: UNCONSUMED, CONSUMED, ALL
+ * 5. other results (aggregate functions with/without using value groups)
*
- * Note: a default [PageSpecification] is applied to the query returning the 1st page (indexed from 0) with up to 200 entries.
- * It is the responsibility of the Client to request further pages and/or specify a more suitable [PageSpecification].
+ * @throws VaultQueryException if the query cannot be executed for any reason
+ * (missing criteria or parsing error, paging errors, unsupported query, underlying database error)
+ *
+ * Notes
+ * If no [PageSpecification] is provided, a maximum of [DEFAULT_PAGE_SIZE] results will be returned.
+ * API users must specify a [PageSpecification] if they are expecting more than [DEFAULT_PAGE_SIZE] results,
+ * otherwise a [VaultQueryException] will be thrown alerting to this condition.
+ * It is the responsibility of the API user to request further pages and/or specify a more suitable [PageSpecification].
*/
// DOCSTART VaultQueryByAPI
@RPCReturnsObservables
@@ -119,59 +131,69 @@ interface CordaRPCOps : RPCOps {
*
* Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
- */
+ */
// DOCSTART VaultTrackByAPI
@RPCReturnsObservables
fun vaultTrackBy(criteria: QueryCriteria,
paging: PageSpecification,
sorting: Sort,
- contractType: Class): Vault.PageAndUpdates
+ contractType: Class): DataFeed, Vault.Update>
// DOCEND VaultTrackByAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations
// Java Helpers
// DOCSTART VaultTrackAPIHelpers
- fun vaultTrack(contractType: Class): Vault.PageAndUpdates {
+ fun vaultTrack(contractType: Class): DataFeed, Vault.Update> {
return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType)
}
- fun vaultTrackByCriteria(contractType: Class, criteria: QueryCriteria): Vault.PageAndUpdates {
+ fun vaultTrackByCriteria(contractType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> {
return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractType)
}
- fun vaultTrackByWithPagingSpec(contractType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.PageAndUpdates {
+ fun vaultTrackByWithPagingSpec(contractType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> {
return vaultTrackBy(criteria, paging, Sort(emptySet()), contractType)
}
- fun vaultTrackByWithSorting(contractType: Class, criteria: QueryCriteria, sorting: Sort): Vault.PageAndUpdates {
+ fun vaultTrackByWithSorting(contractType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> {
return vaultTrackBy(criteria, PageSpecification(), sorting, contractType)
}
// DOCEND VaultTrackAPIHelpers
/**
- * Returns a pair of head states in the vault and an observable of future updates to the vault.
+ * Returns a data feed of head states in the vault and an observable of future updates to the vault.
*/
@RPCReturnsObservables
// TODO: Remove this from the interface
@Deprecated("This function will be removed in a future milestone", ReplaceWith("vaultTrackBy(QueryCriteria())"))
- fun vaultAndUpdates(): Pair>, Observable>
+ fun vaultAndUpdates(): DataFeed>, Vault.Update>
/**
- * Returns a pair of all recorded transactions and an observable of future recorded ones.
+ * Returns a data feed of all recorded transactions and an observable of future recorded ones.
*/
@RPCReturnsObservables
- fun verifiedTransactions(): Pair, Observable>
+ fun verifiedTransactionsFeed(): DataFeed, SignedTransaction>
+
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("verifiedTransactionFeed()"))
+ fun verifiedTransactions() = verifiedTransactionsFeed()
+
/**
* Returns a snapshot list of existing state machine id - recorded transaction hash mappings, and a stream of future
* such mappings as well.
*/
@RPCReturnsObservables
- fun stateMachineRecordedTransactionMapping(): Pair, Observable>
+ fun stateMachineRecordedTransactionMappingFeed(): DataFeed, StateMachineTransactionMapping>
+
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("stateMachineRecordedTransactionMappingFeed()"))
+ fun stateMachineRecordedTransactionMapping() = stateMachineRecordedTransactionMappingFeed()
/**
* Returns all parties currently visible on the network with their advertised services and an observable of future updates to the network.
*/
@RPCReturnsObservables
- fun networkMapUpdates(): Pair, Observable>
+ fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange>
+
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("networkMapFeed()"))
+ fun networkMapUpdates() = networkMapFeed()
/**
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
@@ -282,6 +304,13 @@ interface CordaRPCOps : RPCOps {
/** Enumerates the class names of the flows that this node knows about. */
fun registeredFlows(): List
+
+ /**
+ * Returns a node's identity from the network map cache, where known.
+ *
+ * @return the node info if available.
+ */
+ fun nodeIdentityFromParty(party: AbstractParty): NodeInfo?
}
inline fun CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
@@ -292,7 +321,7 @@ inline fun CordaRPCOps.vaultQueryBy(criteria: QueryC
inline fun CordaRPCOps.vaultTrackBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
paging: PageSpecification = PageSpecification(),
- sorting: Sort = Sort(emptySet())): Vault.PageAndUpdates {
+ sorting: Sort = Sort(emptySet())): DataFeed, Vault.Update> {
return vaultTrackBy(criteria, paging, sorting, T::class.java)
}
@@ -340,6 +369,27 @@ inline fun > CordaRPCOps.startFlow
arg3: D
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
+inline fun > CordaRPCOps.startFlow(
+ @Suppress("UNUSED_PARAMETER")
+ flowConstructor: (A, B, C, D, E) -> R,
+ arg0: A,
+ arg1: B,
+ arg2: C,
+ arg3: D,
+ arg4: E
+): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
+
+inline fun > CordaRPCOps.startFlow(
+ @Suppress("UNUSED_PARAMETER")
+ flowConstructor: (A, B, C, D, E, F) -> R,
+ arg0: A,
+ arg1: B,
+ arg2: C,
+ arg3: D,
+ arg4: E,
+ arg5: F
+): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4, arg5)
+
/**
* Same again, except this time with progress-tracking enabled.
*/
@@ -382,3 +432,18 @@ inline fun > CordaRPCOps.startTrac
arg2: C,
arg3: D
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
+
+/**
+ * The Data feed contains a snapshot of the requested data and an [Observable] of future updates.
+ */
+@CordaSerializable
+data class DataFeed(val snapshot: A, val updates: Observable) {
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("snapshot"))
+ val first: A get() = snapshot
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("updates"))
+ val second: Observable get() = updates
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("snapshot"))
+ val current: A get() = snapshot
+ @Deprecated("This function will be removed in a future milestone", ReplaceWith("updates"))
+ val future: Observable get() = updates
+}
diff --git a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt
index 15f98be512..342e9997e1 100644
--- a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt
+++ b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt
@@ -2,11 +2,10 @@ package net.corda.core.node
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
-import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable
-import org.bouncycastle.cert.X509CertificateHolder
+import net.corda.core.utilities.NetworkHostAndPort
/**
* Information for an advertised service including the service specific identity information.
@@ -18,16 +17,17 @@ data class ServiceEntry(val info: ServiceInfo, val identity: PartyAndCertificate
/**
* Info about a network node that acts on behalf of some form of contract party.
*/
+// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
@CordaSerializable
-data class NodeInfo(val address: SingleMessageRecipient,
- val legalIdentityAndCert: PartyAndCertificate,
+data class NodeInfo(val addresses: List,
+ val legalIdentityAndCert: PartyAndCertificate, //TODO This field will be removed in future PR which gets rid of services.
+ val legalIdentitiesAndCerts: Set,
val platformVersion: Int,
var advertisedServices: List = emptyList(),
- val physicalLocation: PhysicalLocation? = null) {
+ val worldMapLocation: WorldMapLocation? = null) {
init {
require(advertisedServices.none { it.identity == legalIdentityAndCert }) { "Service identities must be different from node legal identity" }
}
-
val legalIdentity: Party
get() = legalIdentityAndCert.party
val notaryIdentity: Party
@@ -35,7 +35,4 @@ data class NodeInfo(val address: SingleMessageRecipient,
fun serviceIdentities(type: ServiceType): List {
return advertisedServices.filter { it.info.type.isSubTypeOf(type) }.map { it.identity.party }
}
- fun servideIdentitiesAndCert(type: ServiceType): List {
- return advertisedServices.filter { it.info.type.isSubTypeOf(type) }.map { it.identity }
- }
}
diff --git a/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt b/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt
index 8d48e3a27f..049845432f 100644
--- a/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt
+++ b/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt
@@ -43,15 +43,15 @@ data class WorldCoordinate(val latitude: Double, val longitude: Double) {
* The [countryCode] field is a two letter ISO country code.
*/
@CordaSerializable
-data class PhysicalLocation(val coordinate: WorldCoordinate, val description: String, val countryCode: String)
+data class WorldMapLocation(val coordinate: WorldCoordinate, val description: String, val countryCode: String)
/**
* A simple lookup table of city names to their coordinates. Lookups are case insensitive.
*/
object CityDatabase {
private val matcher = Regex("^([a-zA-Z- ]*) \\((..)\\)$")
- private val caseInsensitiveLookups = HashMap()
- val cityMap = HashMap()
+ private val caseInsensitiveLookups = HashMap()
+ val cityMap = HashMap()
init {
javaClass.getResourceAsStream("cities.txt").bufferedReader().useLines { lines ->
@@ -60,7 +60,7 @@ object CityDatabase {
val (name, lng, lat) = line.split('\t')
val matchResult = matcher.matchEntire(name) ?: throw Exception("Could not parse line: $line")
val (city, country) = matchResult.destructured
- val location = PhysicalLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), city, country)
+ val location = WorldMapLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), city, country)
caseInsensitiveLookups[city.toLowerCase()] = location
cityMap[city] = location
}
diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
index 13038dc558..081aea4d7f 100644
--- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
+++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
@@ -1,5 +1,6 @@
package net.corda.core.node
+import com.google.common.collect.Lists
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.node.services.*
@@ -17,7 +18,9 @@ import java.time.Clock
*/
interface ServicesForResolution {
val identityService: IdentityService
- val storageService: AttachmentsStorageService
+
+ /** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
+ val attachments: AttachmentStorage
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
@@ -40,7 +43,14 @@ interface ServiceHub : ServicesForResolution {
val vaultService: VaultService
val vaultQueryService: VaultQueryService
val keyManagementService: KeyManagementService
- override val storageService: StorageService
+
+ /**
+ * A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
+ * The signatures aren't technically needed after that point, but we keep them around so that we can relay
+ * the transaction data to other nodes that need it.
+ */
+ val validatedTransactions: TransactionStorage
+
val networkMapCache: NetworkMapCache
val transactionVerifierService: TransactionVerifierService
val clock: Clock
@@ -54,41 +64,41 @@ interface ServiceHub : ServicesForResolution {
fun cordaService(type: Class): T
/**
- * Given a [SignedTransaction], writes it to the local storage for validated transactions and then
- * sends them to the vault for further processing. Expects to be run within a database transaction.
+ * Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
+ * further processing. This is expected to be run within a database transaction.
*
* @param txs The transactions to record.
*/
- // TODO: Make this take a single tx.
fun recordTransactions(txs: Iterable)
/**
- * Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
- * sends them to the vault for further processing.
- *
- * @param txs The transactions to record.
+ * Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
+ * further processing. This is expected to be run within a database transaction.
*/
- fun recordTransactions(vararg txs: SignedTransaction) = recordTransactions(txs.toList())
+ fun recordTransactions(first: SignedTransaction, vararg remaining: SignedTransaction) {
+ recordTransactions(Lists.asList(first, remaining))
+ }
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
- * @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
+ * @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
*/
@Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> {
- val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
- return definingTx.tx.outputs[stateRef.index]
+ val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
+ return stx.tx.outputs[stateRef.index]
}
/**
- * Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol.
+ * Converts the given [StateRef] into a [StateAndRef] object.
*
- * @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
+ * @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
*/
- fun toStateAndRef(ref: StateRef): StateAndRef {
- val definingTx = storageService.validatedTransactions.getTransaction(ref.txhash) ?: throw TransactionResolutionException(ref.txhash)
- return definingTx.tx.outRef(ref.index)
+ @Throws(TransactionResolutionException::class)
+ fun toStateAndRef(stateRef: StateRef): StateAndRef {
+ val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
+ return stx.tx.outRef(stateRef.index)
}
/**
@@ -96,7 +106,7 @@ interface ServiceHub : ServicesForResolution {
* Node's primary signing identity.
* Typical use is during signing in flows and for unit test signing.
* When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService
- * the matching [PrivateKey] will be looked up internally and used to sign.
+ * the matching [java.security.PrivateKey] will be looked up internally and used to sign.
* If the key is actually a CompositeKey, the first leaf key hosted on this node
* will be used to create the signature.
*/
@@ -108,8 +118,8 @@ interface ServiceHub : ServicesForResolution {
* otherwise an IllegalArgumentException will be thrown.
* Typical use is during signing in flows and for unit test signing.
* When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService
- * the matching [PrivateKey] will be looked up internally and used to sign.
- * If the key is actually a [CompositeKey], the first leaf key hosted on this node
+ * the matching [java.security.PrivateKey] will be looked up internally and used to sign.
+ * If the key is actually a [net.corda.core.crypto.CompositeKey], the first leaf key hosted on this node
* will be used to create the signature.
*/
val notaryIdentityKey: PublicKey get() = this.myInfo.notaryIdentity.owningKey
@@ -119,7 +129,7 @@ interface ServiceHub : ServicesForResolution {
* using keys stored inside the node.
* @param builder The [TransactionBuilder] to seal with the node's signature.
* Any existing signatures on the builder will be preserved.
- * @param publicKey The [PublicKey] matched to the internal [PrivateKey] to use in signing this transaction.
+ * @param publicKey The [PublicKey] matched to the internal [java.security.PrivateKey] to use in signing this transaction.
* If the passed in key is actually a CompositeKey the code searches for the first child key hosted within this node
* to sign with.
* @return Returns a SignedTransaction with the new node signature attached.
@@ -130,7 +140,6 @@ interface ServiceHub : ServicesForResolution {
return builder.toSignedTransaction(false)
}
-
/**
* Helper method to construct an initial partially signed transaction from a TransactionBuilder
* using the default identity key contained in the node.
@@ -140,36 +149,35 @@ interface ServiceHub : ServicesForResolution {
*/
fun signInitialTransaction(builder: TransactionBuilder): SignedTransaction = signInitialTransaction(builder, legalIdentityKey)
-
/**
* Helper method to construct an initial partially signed transaction from a [TransactionBuilder]
* using a set of keys all held in this node.
* @param builder The [TransactionBuilder] to seal with the node's signature.
* Any existing signatures on the builder will be preserved.
- * @param signingPubKeys A list of [PublicKeys] used to lookup the matching [PrivateKey] and sign.
+ * @param signingPubKeys A list of [PublicKey]s used to lookup the matching [java.security.PrivateKey] and sign.
* @throws IllegalArgumentException is thrown if any keys are unavailable locally.
* @return Returns a [SignedTransaction] with the new node signature attached.
*/
fun signInitialTransaction(builder: TransactionBuilder, signingPubKeys: Iterable): SignedTransaction {
- var stx: SignedTransaction? = null
- for (pubKey in signingPubKeys) {
- stx = if (stx == null) {
- signInitialTransaction(builder, pubKey)
- } else {
- addSignature(stx, pubKey)
- }
+ val it = signingPubKeys.iterator()
+ var stx = signInitialTransaction(builder, it.next())
+ while (it.hasNext()) {
+ stx = addSignature(stx, it.next())
}
- return stx!!
+ return stx
}
/**
* Helper method to create an additional signature for an existing (partially) [SignedTransaction].
* @param signedTransaction The [SignedTransaction] to which the signature will apply.
- * @param publicKey The [PublicKey] matching to a signing [PrivateKey] hosted in the node.
- * If the [PublicKey] is actually a [CompositeKey] the first leaf key found locally will be used for signing.
- * @return The [DigitalSignature.WithKey] generated by signing with the internally held [PrivateKey].
+ * @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
+ * If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
+ * for signing.
+ * @return The [DigitalSignature.WithKey] generated by signing with the internally held [java.security.PrivateKey].
*/
- fun createSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): DigitalSignature.WithKey = keyManagementService.sign(signedTransaction.id.bytes, publicKey)
+ fun createSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): DigitalSignature.WithKey {
+ return keyManagementService.sign(signedTransaction.id.bytes, publicKey)
+ }
/**
* Helper method to create an additional signature for an existing (partially) SignedTransaction
@@ -177,16 +185,21 @@ interface ServiceHub : ServicesForResolution {
* @param signedTransaction The SignedTransaction to which the signature will apply.
* @return The DigitalSignature.WithKey generated by signing with the internally held identity PrivateKey.
*/
- fun createSignature(signedTransaction: SignedTransaction): DigitalSignature.WithKey = createSignature(signedTransaction, legalIdentityKey)
+ fun createSignature(signedTransaction: SignedTransaction): DigitalSignature.WithKey {
+ return createSignature(signedTransaction, legalIdentityKey)
+ }
/**
* Helper method to append an additional signature to an existing (partially) [SignedTransaction].
* @param signedTransaction The [SignedTransaction] to which the signature will be added.
- * @param publicKey The [PublicKey] matching to a signing [PrivateKey] hosted in the node.
- * If the [PublicKey] is actually a [CompositeKey] the first leaf key found locally will be used for signing.
+ * @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
+ * If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
+ * for signing.
* @return A new [SignedTransaction] with the addition of the new signature.
*/
- fun addSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): SignedTransaction = signedTransaction + createSignature(signedTransaction, publicKey)
+ fun addSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): SignedTransaction {
+ return signedTransaction + createSignature(signedTransaction, publicKey)
+ }
/**
* Helper method to ap-pend an additional signature for an existing (partially) [SignedTransaction]
diff --git a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt
index 93ce39069d..af542a2b43 100644
--- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt
+++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt
@@ -2,21 +2,14 @@ package net.corda.core.node.services
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
+import java.io.IOException
import java.io.InputStream
-import java.nio.file.Path
+import java.nio.file.FileAlreadyExistsException
/**
* An attachment store records potentially large binary objects, identified by their hash.
*/
interface AttachmentStorage {
- /**
- * If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
- * human browsing convenience: the attachment itself will still be the file (that is, edits to the extracted directory
- * will not have any effect).
- */
- var automaticallyExtractAttachments: Boolean
- var storePath: Path
-
/**
* Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open
* a stream for the data, which will be a zip/jar file.
@@ -27,13 +20,14 @@ interface AttachmentStorage {
* Inserts the given attachment into the store, does *not* close the input stream. This can be an intensive
* operation due to the need to copy the bytes to disk and hash them along the way.
*
- * Note that you should not pass a [JarInputStream] into this method and it will throw if you do, because access
- * to the raw byte stream is required.
+ * Note that you should not pass a [java.util.jar.JarInputStream] into this method and it will throw if you do, because
+ * access to the raw byte stream is required.
*
* @throws FileAlreadyExistsException if the given byte stream has already been inserted.
- * @throws IllegalArgumentException if the given byte stream is empty or a [JarInputStream].
+ * @throws IllegalArgumentException if the given byte stream is empty or a [java.util.jar.JarInputStream].
* @throws IOException if something went wrong.
*/
+ @Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream): SecureHash
}
diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt
index 027383031d..cda2fc7c5f 100644
--- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt
+++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt
@@ -2,8 +2,11 @@ package net.corda.core.node.services
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.Contract
+import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
+import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
+import net.corda.core.node.ServiceHub
import net.corda.core.randomOrNull
import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.asn1.x500.X500Name
@@ -48,7 +51,7 @@ interface NetworkMapCache {
* Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the
* first subscriber is registered so as to avoid racing with early updates.
*/
- fun track(): Pair, Observable>
+ fun track(): DataFeed, MapChange>
/** Get the collection of nodes which advertise a specific service. */
fun getNodesWithService(serviceType: ServiceType): List {
@@ -62,6 +65,17 @@ interface NetworkMapCache {
*/
fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = getNodesWithService(type).firstOrNull()
+ /**
+ * Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party
+ * is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this
+ * returns null.
+ *
+ * @param party party to retrieve node information for.
+ * @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is
+ * no node for the party, only that this cache is unaware of it.
+ */
+ fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo?
+
/** Look up the node info for a legal name. */
fun getNodeByLegalName(principal: X500Name): NodeInfo? = partyNodes.singleOrNull { it.legalIdentity.name == principal }
diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt
new file mode 100644
index 0000000000..aa3def742c
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt
@@ -0,0 +1,78 @@
+package net.corda.core.node.services
+
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TimeWindow
+import net.corda.core.crypto.DigitalSignature
+import net.corda.core.crypto.SecureHash
+import net.corda.core.crypto.SignedData
+import net.corda.core.flows.FlowLogic
+import net.corda.core.identity.Party
+import net.corda.core.node.ServiceHub
+import net.corda.core.serialization.SingletonSerializeAsToken
+import net.corda.core.serialization.serialize
+import net.corda.core.utilities.loggerFor
+import net.corda.flows.NotaryError
+import net.corda.flows.NotaryException
+import org.slf4j.Logger
+
+abstract class NotaryService : SingletonSerializeAsToken() {
+ abstract val services: ServiceHub
+
+ abstract fun start()
+ abstract fun stop()
+
+ /**
+ * Produces a notary service flow which has the corresponding sends and receives as [NotaryFlow.Client].
+ * The first parameter is the client [Party] making the request and the second is the platform version
+ * of the client's node. Use this version parameter to provide backwards compatibility if the notary flow protocol
+ * changes.
+ */
+ abstract fun createServiceFlow(otherParty: Party, platformVersion: Int): FlowLogic
+}
+
+/**
+ * A base notary service implementation that provides functionality for cases where a signature by a single member
+ * of the cluster is sufficient for transaction notarisation. For example, a single-node or a Raft notary.
+ */
+abstract class TrustedAuthorityNotaryService : NotaryService() {
+ protected open val log: Logger = loggerFor