diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000000..37dc57e9fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,8 @@
+Thank you for choosing to report an issue with Corda.
+
+When reporting an issue please make sure it contains;
+
+* A clear description of the issue
+* Any logs or stack traces that you can provide (use a site like https://pastebin.com for larger logs)
+* Steps to reproduce the issue
+* The version/tag/release or commit hash it occured on
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..62d77dd2d8
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,15 @@
+Thank you for choosing to contribute to Corda.
+
+Your PR must be approved by one or more reviewers and all tests must be passed on TeamCity (https://ci.corda.r3cev.com) in order to be merged.
+
+Once you have submitted a PR you are responsible for keeping it up to date until the time it is merged.
+
+PR Checklist:
+
+1. Ensure any new code is tested as described in https://docs.corda.net/testing.html
+2. Ensure you have done any relevant automated testing and manual testing
+3. Add your changes to docs/source/changelog.rst
+4. Update any documentation in docs/source relating to your changes and learn how to build them in https://docs.corda.net/building-the-docs.html
+5. If you are contributing for the first time please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you have read, and agreed to, the agreement.
+
+Please remove this message when you have read it.
diff --git a/.gitignore b/.gitignore
index 5a11950686..710d20ef3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ local.properties
**/build/*
lib/dokka.jar
+lib/quasar.jar
**/logs/*
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 75643b8eeb..4edce38213 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -62,8 +62,9 @@
-
-
+
+
+
@@ -91,13 +92,15 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 63b95c82ca..dd7a6af5da 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
// Our version: bump this on release.
- ext.corda_release_version = "0.15-SNAPSHOT"
+ ext.corda_release_version = "0.16-SNAPSHOT"
// Increment this on any release that changes public APIs anywhere in the Corda platform
// TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal
ext.corda_platform_version = 1
@@ -143,10 +143,6 @@ allprojects {
mavenLocal()
mavenCentral()
jcenter()
- // TODO: remove this once we eliminate Exposed
- maven {
- url 'https://dl.bintray.com/kotlin/exposed'
- }
maven { url 'https://jitpack.io' }
}
@@ -220,15 +216,15 @@ tasks.withType(Test) {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
- networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB"
+ networkMap "O=Controller,OU=corda,L=London,C=GB"
node {
- name "CN=Controller,O=R3,OU=corda,L=London,C=GB"
+ name "O=Controller,OU=corda,L=London,C=GB"
advertisedServices = ["corda.notary.validating"]
p2pPort 10002
cordapps = []
}
node {
- name "CN=Bank A,O=R3,OU=corda,L=London,C=GB"
+ name "O=Bank A,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10012
rpcPort 10013
@@ -236,7 +232,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
cordapps = []
}
node {
- name "CN=Bank B,O=R3,OU=corda,L=London,C=GB"
+ name "O=Bank B,OU=corda,L=London,C=GB"
advertisedServices = []
p2pPort 10007
rpcPort 10008
@@ -256,7 +252,7 @@ bintrayConfig {
projectUrl = 'https://github.com/corda/corda'
gpgSign = true
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
- publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver']
+ publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'
@@ -291,7 +287,7 @@ artifactory {
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
defaults {
- publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver')
+ publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver')
}
}
}
diff --git a/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
similarity index 99%
rename from client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt
rename to client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
index cf2ed02f52..a8418809da 100644
--- a/client/jackson/src/main/kotlin/net/corda/jackson/JacksonSupport.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
@@ -1,4 +1,4 @@
-package net.corda.jackson
+package net.corda.client.jackson
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
@@ -12,7 +12,7 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
-import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
@@ -27,6 +27,8 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
+import net.corda.core.utilities.parsePublicKeyBase58
+import net.corda.core.utilities.toBase58String
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.x500.X500Name
import java.math.BigDecimal
diff --git a/client/jackson/src/main/kotlin/net/corda/jackson/StringToMethodCallParser.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt
similarity index 99%
rename from client/jackson/src/main/kotlin/net/corda/jackson/StringToMethodCallParser.kt
rename to client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt
index 45454ef1d0..ddbf924c27 100644
--- a/client/jackson/src/main/kotlin/net/corda/jackson/StringToMethodCallParser.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt
@@ -1,11 +1,11 @@
-package net.corda.jackson
+package net.corda.client.jackson
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap
-import net.corda.jackson.StringToMethodCallParser.ParsedMethodCall
+import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
import org.slf4j.LoggerFactory
import java.lang.reflect.Constructor
import java.lang.reflect.Method
@@ -223,4 +223,4 @@ open class StringToMethodCallParser @JvmOverloads constructor(
Pair(name, argStr)
}.toMap()
}
-}
\ No newline at end of file
+}
diff --git a/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
similarity index 98%
rename from client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt
rename to client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
index 13acd147ab..fec036aeb2 100644
--- a/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
@@ -1,4 +1,4 @@
-package net.corda.jackson
+package net.corda.client.jackson
import com.fasterxml.jackson.databind.SerializationFeature
import net.corda.core.contracts.Amount
diff --git a/client/jackson/src/test/kotlin/net/corda/jackson/StringToMethodCallParserTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt
similarity index 98%
rename from client/jackson/src/test/kotlin/net/corda/jackson/StringToMethodCallParserTest.kt
rename to client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt
index 7610fbf91b..a93052a48e 100644
--- a/client/jackson/src/test/kotlin/net/corda/jackson/StringToMethodCallParserTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt
@@ -1,4 +1,4 @@
-package net.corda.jackson
+package net.corda.client.jackson
import net.corda.core.crypto.SecureHash
import org.junit.Assert.assertArrayEquals
@@ -65,4 +65,4 @@ class StringToMethodCallParserTest {
val args: Array = parser.parseArguments(clazz.name, names.zip(ctor.parameterTypes), "alternativeWord: Foo bar!")
assertArrayEquals(args, arrayOf("Foo bar!"))
}
-}
\ No newline at end of file
+}
diff --git a/client/jfx/build.gradle b/client/jfx/build.gradle
index 8b8c677e4b..ef9602e810 100644
--- a/client/jfx/build.gradle
+++ b/client/jfx/build.gradle
@@ -50,6 +50,7 @@ dependencies {
// Integration test helpers
integrationTestCompile "junit:junit:$junit_version"
+ integrationTestCompile project(':node-driver')
}
task integrationTest(type: Test) {
diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt
index 83453feddd..20e9c8dda5 100644
--- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt
+++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt
@@ -58,13 +58,13 @@ class NodeMonitorModelTest : DriverBasedTest() {
startFlowPermission(),
startFlowPermission())
)
- val aliceNodeFuture = startNode(ALICE.name, rpcUsers = listOf(cashUser))
- val notaryNodeFuture = startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
+ val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
+ val notaryNodeFuture = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
val notaryNodeHandle = notaryNodeFuture.getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo
notaryNode = notaryNodeHandle.nodeInfo
- newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
+ newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
@@ -76,7 +76,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false)
rpc = monitor.proxyObservable.value!!
- val bobNodeHandle = startNode(BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
+ val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
bobNode = bobNodeHandle.nodeInfo
val monitorBob = NodeMonitorModel()
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
index 243d87843e..636603d1ab 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
@@ -3,25 +3,30 @@ package net.corda.client.jfx.model
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import net.corda.core.contracts.Amount
+import java.math.BigDecimal
+import java.math.RoundingMode
import java.util.*
-
-interface ExchangeRate {
- fun rate(from: Currency, to: Currency): Double
-}
-
-fun ExchangeRate.exchangeAmount(amount: Amount, to: Currency) =
- Amount(exchangeDouble(amount, to).toLong(), to)
-
-fun ExchangeRate.exchangeDouble(amount: Amount, to: Currency) =
- rate(amount.token, to) * amount.quantity
-
/**
* This model provides an exchange rate from arbitrary currency to arbitrary currency.
- * TODO hook up an actual oracle
*/
+abstract class ExchangeRate {
+ /**
+ * Convert the given amount of a currency into the target currency.
+ *
+ * @return the original amount converted to an amount in the target currency.
+ */
+ fun exchangeAmount(amount: Amount, to: Currency) = Amount.fromDecimal(amount.toDecimal().multiply(rate(amount.token, to)), to)
+
+ abstract fun rate(from: Currency, to: Currency): BigDecimal
+}
+
+/**
+ * Default implementation of an exchange rate model, which uses a fixed exchange rate.
+ */
+// TODO hook up an actual oracle
class ExchangeRateModel {
- val exchangeRate: ObservableValue = SimpleObjectProperty(object : ExchangeRate {
- override fun rate(from: Currency, to: Currency) = 1.0
+ val exchangeRate: ObservableValue = SimpleObjectProperty(object : ExchangeRate() {
+ override fun rate(from: Currency, to: Currency) = BigDecimal.ONE
})
}
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
index ee47ed9947..923465b2b9 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
@@ -1,13 +1,14 @@
package net.corda.client.jfx.model
+import com.google.common.cache.CacheBuilder
+import com.google.common.cache.CacheLoader
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
-import net.corda.client.jfx.utils.firstOrDefault
-import net.corda.client.jfx.utils.firstOrNullObservable
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
-import net.corda.core.crypto.keys
+import net.corda.core.identity.AbstractParty
+import net.corda.core.identity.AnonymousParty
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache.MapChange
import java.security.PublicKey
@@ -29,6 +30,12 @@ class NetworkIdentityModel {
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
+ private val identityCache = CacheBuilder.newBuilder()
+ .build>(CacheLoader.from {
+ publicKey ->
+ publicKey?.let { rpcProxy.map { it?.nodeIdentityFromParty(AnonymousParty(publicKey)) } }
+ })
+
val parties: ObservableList = networkIdentities.filtered { !it.isCordaService() }
val notaries: ObservableList = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
@@ -38,8 +45,5 @@ class NetworkIdentityModel {
return advertisedServices.any { it.info.type.isNetworkMap() || it.info.type.isNotary() }
}
- // TODO: Use Identity Service in service hub instead?
- fun lookup(publicKey: PublicKey): ObservableValue = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey.keys.any { it == publicKey } }) {
- it.legalIdentity.owningKey.keys.any { it == publicKey }
- }
+ fun partyFromPublicKey(publicKey: PublicKey): ObservableValue = identityCache[publicKey]
}
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
index 33e160c742..7bd9623f52 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
@@ -89,7 +89,7 @@ class NodeMonitorModel {
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
// Transactions
- val (transactions, newTransactions) = proxy.verifiedTransactionsFeed()
+ val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed()
newTransactions.startWith(transactions).subscribe(transactionsSubject)
// SM -> TX mapping
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt
index 26387c1663..49e76e227a 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/AmountBindings.kt
@@ -29,7 +29,7 @@ object AmountBindings {
return EasyBind.combine(observableCurrency, observableExchangeRate) { currency, exchangeRate ->
Pair) -> Long>(
currency,
- { (quantity, _, token) -> (exchangeRate.rate(token, currency) * quantity).toLong() }
+ { amount -> exchangeRate.exchangeAmount(amount, currency).quantity }
)
}
}
diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle
index 70ebc5ed57..2b3c020f49 100644
--- a/client/rpc/build.gradle
+++ b/client/rpc/build.gradle
@@ -67,7 +67,7 @@ dependencies {
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
- testCompile project(':test-utils')
+ testCompile project(':node-driver')
testCompile project(':client:mock')
// Smoke tests do NOT have any Node code on the classpath!
diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java
index 2cd3ad6628..5a8a8349eb 100644
--- a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java
+++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java
@@ -6,12 +6,12 @@ import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.messaging.FlowHandle;
import net.corda.core.node.NodeInfo;
import net.corda.core.utilities.OpaqueBytes;
+import net.corda.core.utilities.X500NameUtils;
import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow;
import net.corda.nodeapi.User;
import net.corda.smoketesting.NodeConfig;
import net.corda.smoketesting.NodeProcess;
-import org.bouncycastle.asn1.x500.X500Name;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -43,7 +43,7 @@ public class StandaloneCordaRPCJavaClientTest {
private NodeInfo notaryNode;
private NodeConfig notaryConfig = new NodeConfig(
- new X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
+ X500NameUtils.getX500Name("Notary Service", "Zurich", "CH"),
port.getAndIncrement(),
port.getAndIncrement(),
port.getAndIncrement(),
diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt
index a8c670dc2a..4fab64c88f 100644
--- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt
+++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt
@@ -9,10 +9,7 @@ import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
-import net.corda.core.utilities.OpaqueBytes
-import net.corda.core.utilities.getOrThrow
-import net.corda.core.utilities.loggerFor
-import net.corda.core.utilities.seconds
+import net.corda.core.utilities.*
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS
@@ -26,7 +23,6 @@ import net.corda.nodeapi.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import org.apache.commons.io.output.NullOutputStream
-import org.bouncycastle.asn1.x500.X500Name
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -58,12 +54,12 @@ class StandaloneCordaRPClientTest {
private lateinit var notaryNode: NodeInfo
private val notaryConfig = NodeConfig(
- legalName = X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
- p2pPort = port.andIncrement,
- rpcPort = port.andIncrement,
- webPort = port.andIncrement,
- extraServices = listOf("corda.notary.validating"),
- users = listOf(user)
+ legalName = getX500Name(O = "Notary Service", OU = "corda", L = "Zurich", C = "CH"),
+ p2pPort = port.andIncrement,
+ rpcPort = port.andIncrement,
+ webPort = port.andIncrement,
+ extraServices = listOf("corda.notary.validating"),
+ users = listOf(user)
)
@Before
diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt
new file mode 100644
index 0000000000..378a41f783
--- /dev/null
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt
@@ -0,0 +1,64 @@
+package net.corda.client.rpc
+
+import com.esotericsoftware.kryo.KryoException
+import net.corda.core.concurrent.CordaFuture
+import net.corda.core.internal.concurrent.openFuture
+import net.corda.core.messaging.*
+import net.corda.core.utilities.getOrThrow
+import net.corda.testing.rpcDriver
+import net.corda.testing.startRpcClient
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.Test
+
+class RPCFailureTests {
+ class Unserializable
+ interface Ops : RPCOps {
+ fun getUnserializable(): Unserializable
+ fun getUnserializableAsync(): CordaFuture
+ fun kotlinNPE()
+ fun kotlinNPEAsync(): CordaFuture
+ }
+
+ class OpsImpl : Ops {
+ override val protocolVersion = 1
+ override fun getUnserializable() = Unserializable()
+ override fun getUnserializableAsync(): CordaFuture {
+ return openFuture().apply { capture { getUnserializable() } }
+ }
+
+ override fun kotlinNPE() {
+ (null as Any?)!!.hashCode()
+ }
+
+ override fun kotlinNPEAsync(): CordaFuture {
+ return openFuture().apply { capture { kotlinNPE() } }
+ }
+ }
+
+ private fun rpc(proc: (Ops) -> Any?): Unit = rpcDriver {
+ val server = startRpcServer(ops = OpsImpl()).getOrThrow()
+ proc(startRpcClient(server.broker.hostAndPort!!).getOrThrow())
+ }
+
+ @Test
+ fun `kotlin NPE`() = rpc {
+ assertThatThrownBy { it.kotlinNPE() }.isInstanceOf(KotlinNullPointerException::class.java)
+ }
+
+ @Test
+ fun `kotlin NPE async`() = rpc {
+ val future = it.kotlinNPEAsync()
+ assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KotlinNullPointerException::class.java)
+ }
+
+ @Test
+ fun `unserializable`() = rpc {
+ assertThatThrownBy { it.getUnserializable() }.isInstanceOf(KryoException::class.java)
+ }
+
+ @Test
+ fun `unserializable async`() = rpc {
+ val future = it.getUnserializableAsync()
+ assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KryoException::class.java)
+ }
+}
diff --git a/config/dev/generalnodea.conf b/config/dev/generalnodea.conf
index 94b08cfc0c..16eb90f526 100644
--- a/config/dev/generalnodea.conf
+++ b/config/dev/generalnodea.conf
@@ -1,4 +1,4 @@
-myLegalName : "CN=Bank A,O=Bank A,L=London,C=GB"
+myLegalName : "O=Bank A,L=London,C=GB"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
p2pAddress : "localhost:10002"
@@ -7,6 +7,6 @@ webAddress : "localhost:10004"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:10000"
- legalName : "CN=Network Map Service,O=R3,OU=corda,L=London,C=GB"
+ legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
}
useHTTPS : false
diff --git a/config/dev/generalnodeb.conf b/config/dev/generalnodeb.conf
index 0fe9e66d9c..1eb839aece 100644
--- a/config/dev/generalnodeb.conf
+++ b/config/dev/generalnodeb.conf
@@ -7,6 +7,6 @@ webAddress : "localhost:10007"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:10000"
- legalName : "CN=Network Map Service,O=R3,OU=corda,L=London,C=GB"
+ legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
}
useHTTPS : false
diff --git a/config/dev/nameservernode.conf b/config/dev/nameservernode.conf
index 154f88aad9..664f2b6816 100644
--- a/config/dev/nameservernode.conf
+++ b/config/dev/nameservernode.conf
@@ -1,4 +1,4 @@
-myLegalName : "CN=Notary Service,O=R3,OU=corda,L=London,C=GB"
+myLegalName : "O=Notary Service,OU=corda,L=London,C=GB"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
p2pAddress : "localhost:10000"
diff --git a/constants.properties b/constants.properties
index f8c76f96a6..68c33706ba 100644
--- a/constants.properties
+++ b/constants.properties
@@ -1,5 +1,5 @@
-gradlePluginsVersion=0.15.1
+gradlePluginsVersion=0.16.1
kotlinVersion=1.1.4
guavaVersion=21.0
bouncycastleVersion=1.57
-typesafeConfigVersion=1.3.1
\ No newline at end of file
+typesafeConfigVersion=1.3.1
diff --git a/core/build.gradle b/core/build.gradle
index ca33b3b725..655067d680 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -22,12 +22,15 @@ dependencies {
// Bring in the MockNode infrastructure for writing protocol unit tests.
testCompile project(":node")
- testCompile project(":test-utils")
+ testCompile project(":node-driver")
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+ // Quasar, for suspendable fibres.
+ compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
+
// Thread safety annotations
compile "com.google.code.findbugs:jsr305:3.0.1"
@@ -65,6 +68,15 @@ dependencies {
compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
}
+// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)
+task copyQuasarJar(type: Copy) {
+ from configurations.quasar
+ into "$project.rootProject.projectDir/lib"
+ rename { filename -> "quasar.jar"}
+}
+
+jar.finalizedBy(copyQuasarJar)
+
configurations {
testArtifacts.extendsFrom testRuntime
}
diff --git a/core/src/main/kotlin/net/corda/core/contracts/Amount.kt b/core/src/main/kotlin/net/corda/core/contracts/Amount.kt
index 6de842ff63..43101b2a3e 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/Amount.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/Amount.kt
@@ -1,6 +1,6 @@
package net.corda.core.contracts
-import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.exactAdd
diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
new file mode 100644
index 0000000000..b55055e529
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
@@ -0,0 +1,42 @@
+package net.corda.core.contracts
+
+import net.corda.core.identity.Party
+import net.corda.core.internal.extractFile
+import java.io.FileNotFoundException
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.jar.JarInputStream
+
+/**
+ * An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
+ * contain public static data which can be referenced from transactions and utilised from contracts. Good examples
+ * of how attachments are meant to be used include:
+ * - Calendar data
+ * - Fixes (e.g. LIBOR)
+ * - Smart contract code
+ * - Legal documents
+ * - Facts generated by oracles which might be reused a lot
+ */
+interface Attachment : NamedByHash {
+ fun open(): InputStream
+ fun openAsJAR(): JarInputStream {
+ val stream = open()
+ try {
+ return JarInputStream(stream)
+ } catch (t: Throwable) {
+ stream.use { throw t }
+ }
+ }
+
+ /**
+ * Finds the named file case insensitively and copies it to the output stream.
+ * @throws FileNotFoundException if the given path doesn't exist in the attachment.
+ */
+ fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }
+
+ /**
+ * The parties that have correctly signed the whole attachment.
+ * Can be empty, for example non-contract attachments won't be necessarily be signed.
+ */
+ val signers: List
+}
diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt
index c1d43020f3..51036f8dfe 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt
@@ -32,33 +32,33 @@ inline fun requireThat(body: Requirements.() -> R) = Requirements.body()
// TODO: Provide a version of select that interops with Java
/** Filters the command list by type, party and public key all at once. */
-inline fun Collection>.select(signer: PublicKey? = null,
+inline fun Collection>.select(signer: PublicKey? = null,
party: AbstractParty? = null) =
filter { it.value is T }.
filter { if (signer == null) true else signer in it.signers }.
filter { if (party == null) true else party in it.signingParties }.
- map { AuthenticatedObject(it.signers, it.signingParties, it.value as T) }
+ map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
// TODO: Provide a version of select that interops with Java
/** Filters the command list by type, parties and public keys all at once. */
-inline fun Collection>.select(signers: Collection?,
+inline fun Collection>.select(signers: Collection?,
parties: Collection?) =
filter { it.value is T }.
filter { if (signers == null) true else it.signers.containsAll(signers) }.
filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
- map { AuthenticatedObject(it.signers, it.signingParties, it.value as T) }
+ map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
-inline fun Collection>.requireSingleCommand() = try {
+inline fun Collection>.requireSingleCommand() = try {
select().single()
} catch (e: NoSuchElementException) {
throw IllegalStateException("Required ${T::class.qualifiedName} command") // Better error message.
}
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
-fun Collection>.requireSingleCommand(klass: Class) =
- mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject else null }.single()
+fun Collection>.requireSingleCommand(klass: Class) =
+ mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as CommandWithParties else null }.single()
/**
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
@@ -67,7 +67,7 @@ fun Collection>.requireSingle
*/
@Throws(IllegalArgumentException::class)
inline fun verifyMoveCommand(inputs: List,
- commands: List>)
+ commands: List>)
: MoveCommand {
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
// see a signature from each of those keys. The actual signatures have been verified against the transaction
diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
index a877e98b9f..820d87fe6a 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt
@@ -9,18 +9,11 @@ import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.MissingAttachmentsException
-import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
import java.security.PublicKey
import java.time.Instant
-import java.util.jar.JarInputStream
/** Implemented by anything that can be named by a secure hash value (e.g. transactions, attachments). */
interface NamedByHash {
@@ -219,11 +212,6 @@ interface LinearState : ContractState {
* except at issuance/termination.
*/
val linearId: UniqueIdentifier
-
- /**
- * True if this should be tracked by our vault(s).
- */
- fun isRelevant(ourKeys: Set): Boolean
}
// DOCEND 2
@@ -289,7 +277,7 @@ abstract class TypeOnlyCommandData : CommandData {
data class Command(val value: T, val signers: List) {
// TODO Introduce NonEmptyList?
init {
- require(signers.isNotEmpty())
+ require(signers.isNotEmpty()) { "The list of signers cannot be empty" }
}
constructor(data: T, key: PublicKey) : this(data, listOf(key))
@@ -312,9 +300,9 @@ interface MoveCommand : CommandData {
data class UpgradeCommand(val upgradedContractClass: Class>) : CommandData
// DOCSTART 6
-/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
+/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@CordaSerializable
-data class AuthenticatedObject(
+data class CommandWithParties(
val signers: List,
/** If any public keys were recognised, the looked up institutions are available here */
val signingParties: List,
@@ -367,69 +355,6 @@ interface UpgradedContract ByteArray) : Attachment {
- companion object {
- fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
- return {
- val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
- (a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
- }
- }
- }
-
- protected val attachmentData: ByteArray by lazy(dataLoader)
- override fun open(): InputStream = attachmentData.inputStream()
- override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
- override fun hashCode() = id.hashCode()
- override fun toString() = "${javaClass.simpleName}(id=$id)"
-}
-
-@Throws(IOException::class)
-fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
- val p = path.toLowerCase().split('\\', '/')
- while (true) {
- val e = nextJarEntry ?: break
- if (!e.isDirectory && e.name.toLowerCase().split('\\', '/') == p) {
- copyTo(outputTo)
- return
- }
- closeEntry()
- }
- throw FileNotFoundException(path)
-}
-
/**
* A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
similarity index 89%
rename from core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
index a1e31e2f01..3d7824751d 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt
@@ -1,12 +1,8 @@
-package net.corda.core.crypto.composite
+package net.corda.core.crypto
-import net.corda.core.crypto.Crypto
-import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight
-import net.corda.core.crypto.keys
-import net.corda.core.crypto.provider.CordaObjectIdentifier
-import net.corda.core.crypto.toStringShort
-import net.corda.core.utilities.exactAdd
+import net.corda.core.crypto.CompositeKey.NodeAndWeight
import net.corda.core.serialization.CordaSerializable
+import net.corda.core.utilities.exactAdd
import net.corda.core.utilities.sequence
import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
@@ -15,8 +11,10 @@ import java.security.PublicKey
import java.util.*
/**
- * A tree data structure that enables the representation of composite public keys.
- * Notice that with that implementation CompositeKey extends PublicKey. Leaves are represented by single public keys.
+ * A tree data structure that enables the representation of composite public keys, which are used to represent
+ * the signing requirements for multisignature scenarios such as RAFT notary services. A composite key is a list
+ * of leaf keys and their contributing weight, and each leaf can be a conventional single key or a composite key.
+ * Keys contribute their weight to the total if they are matched by the signature.
*
* For complex scenarios, such as *"Both Alice and Bob need to sign to consume a state S"*, we can represent
* the requirement by creating a tree with a root [CompositeKey], and Alice and Bob as children.
@@ -26,9 +24,7 @@ import java.util.*
* Using these constructs we can express e.g. 1 of N (OR) or N of N (AND) signature requirements. By nesting we can
* create multi-level requirements such as *"either the CEO or 3 of 5 of his assistants need to sign"*.
*
- * [CompositeKey] maintains a list of [NodeAndWeight]s which holds child subtree with associated weight carried by child node signatures.
- *
- * The [threshold] specifies the minimum total weight required (in the simple case – the minimum number of child
+ * @property threshold specifies the minimum total weight required (in the simple case – the minimum number of child
* signatures required) to satisfy the sub-tree rooted at this node.
*/
@CordaSerializable
@@ -163,7 +159,7 @@ class CompositeKey private constructor(val threshold: Int, children: List
get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
@@ -253,9 +249,14 @@ class CompositeKey private constructor(val threshold: Int, children: List 0)
val n = children.size
return if (n > 1)
CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children)
@@ -265,15 +266,7 @@ class CompositeKey private constructor(val threshold: Int, children: List.expandedCompositeKeys: Set
- get() = flatMap { it.keys }.toSet()
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt
similarity index 82%
rename from core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt
index b933188d88..df7fc7c433 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/composite/KeyFactory.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt
@@ -1,11 +1,14 @@
-package net.corda.core.crypto.composite
+package net.corda.core.crypto
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.KeySpec
import java.security.spec.X509EncodedKeySpec
-class KeyFactory : KeyFactorySpi() {
+/**
+ * Factory for generating composite keys from ASN.1 format key specifications. This is used by [CordaSecurityProvider].
+ */
+class CompositeKeyFactory : KeyFactorySpi() {
@Throws(InvalidKeySpecException::class)
override fun engineGeneratePrivate(keySpec: KeySpec): PrivateKey {
@@ -23,7 +26,7 @@ class KeyFactory : KeyFactorySpi() {
@Throws(InvalidKeySpecException::class)
override fun engineGetKeySpec(key: Key, keySpec: Class): T {
- // Only support [X509EncodedKeySpec].
+ // Only support X509EncodedKeySpec.
throw InvalidKeySpecException("Not implemented yet $key $keySpec")
}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
similarity index 94%
rename from core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
index eb231cb7eb..0438a630ae 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeSignature.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt
@@ -1,6 +1,6 @@
-package net.corda.core.crypto.composite
+package net.corda.core.crypto
-import net.corda.core.crypto.SecureHash
+import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
import net.corda.core.serialization.deserialize
import java.io.ByteArrayOutputStream
import java.security.*
@@ -27,6 +27,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
return signatureState!!
}
+ @Deprecated("Deprecated in inherited API")
@Throws(InvalidAlgorithmParameterException::class)
override fun engineGetParameter(param: String?): Any {
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
@@ -46,6 +47,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
}
}
+ @Deprecated("Deprecated in inherited API")
@Throws(InvalidAlgorithmParameterException::class)
override fun engineSetParameter(param: String?, value: Any?) {
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
diff --git a/core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
similarity index 72%
rename from core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt
rename to core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
index 63f3abcf31..eaae5e2ffb 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/provider/CordaSecurityProvider.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
@@ -1,7 +1,5 @@
-package net.corda.core.crypto.provider
+package net.corda.core.crypto
-import net.corda.core.crypto.composite.CompositeKey
-import net.corda.core.crypto.composite.CompositeSignature
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.AccessController
import java.security.PrivilegedAction
@@ -17,20 +15,22 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
}
private fun setup() {
- put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.composite.KeyFactory")
- put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.composite.CompositeSignature")
+ put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.CompositeKeyFactory")
+ put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.CompositeSignature")
val compositeKeyOID = CordaObjectIdentifier.COMPOSITE_KEY.id
put("Alg.Alias.KeyFactory.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
put("Alg.Alias.KeyFactory.OID.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
- put("Alg.Alias.Signature.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
- put("Alg.Alias.Signature.OID.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
+
+ val compositeSignatureOID = CordaObjectIdentifier.COMPOSITE_SIGNATURE.id
+ put("Alg.Alias.Signature.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM)
+ put("Alg.Alias.Signature.OID.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM)
}
}
object CordaObjectIdentifier {
// UUID-based OID
- // TODO: Register for an OID space and issue our own shorter OID
+ // TODO: Register for an OID space and issue our own shorter OID.
@JvmField val COMPOSITE_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
@JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
index 8b1d9fb542..c5f6b16256 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -1,9 +1,5 @@
package net.corda.core.crypto
-import net.corda.core.crypto.composite.CompositeKey
-import net.corda.core.crypto.composite.CompositeSignature
-import net.corda.core.crypto.provider.CordaObjectIdentifier
-import net.corda.core.crypto.provider.CordaSecurityProvider
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
@@ -201,7 +197,7 @@ object Crypto {
private val providerMap: Map = mapOf(
BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(),
CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(),
- "BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
+ "BCPQC" to BouncyCastlePQCProvider()) // Unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
putAll(EdDSASecurityProvider())
diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
index f41fd55f6a..8e6af2cd37 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
@@ -2,8 +2,9 @@
package net.corda.core.crypto
-import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.utilities.OpaqueBytes
+import net.corda.core.utilities.toBase58
+import net.corda.core.utilities.toSHA256Bytes
import java.math.BigInteger
import net.corda.core.utilities.SgxSupport
import java.security.*
@@ -17,13 +18,9 @@ import java.security.*
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
-fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature {
- return DigitalSignature(Crypto.doSign(this, bytesToSign))
-}
+fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign))
-fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
- return DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
-}
+fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
/**
* Helper function to sign with a key pair.
@@ -81,32 +78,27 @@ fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature) : Boolean
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
-val PublicKey.keys: Set get() {
- return if (this is CompositeKey) this.leafKeys
- else setOf(this)
-}
+val PublicKey.keys: Set get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
-fun PublicKey.isFulfilledBy(otherKeys: Iterable): Boolean {
- return if (this is CompositeKey) this.isFulfilledBy(otherKeys)
- else this in otherKeys
-}
+fun PublicKey.isFulfilledBy(otherKeys: Iterable): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
-/** Checks whether any of the given [keys] matches a leaf on the CompositeKey tree or a single PublicKey */
+/** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */
fun PublicKey.containsAny(otherKeys: Iterable): Boolean {
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
else this in otherKeys
}
-/** Returns the set of all [PublicKey]s of the signatures */
+/** Returns the set of all [PublicKey]s of the signatures. */
fun Iterable.byKeys() = map { it.by }.toSet()
-// Allow Kotlin destructuring: val (private, public) = keyPair
+// Allow Kotlin destructuring:
+// val (private, public) = keyPair
operator fun KeyPair.component1(): PrivateKey = this.private
operator fun KeyPair.component2(): PublicKey = this.public
-/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
+/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */
fun generateKeyPair(): KeyPair = Crypto.generateKeyPair()
/**
@@ -147,13 +139,11 @@ fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Cr
* @param numOfBytes how many random bytes to output.
* @return a random [ByteArray].
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
- * or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
+ * or if no strong [SecureRandom] implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
* which should never happen and suggests an unusual JVM or non-standard Java library.
*/
@Throws(NoSuchAlgorithmException::class)
-fun secureRandomBytes(numOfBytes: Int): ByteArray {
- return newSecureRandom().generateSeed(numOfBytes)
-}
+fun secureRandomBytes(numOfBytes: Int): ByteArray = newSecureRandom().generateSeed(numOfBytes)
/**
* This is a hack added because during deserialisation when no-param constructors are called sometimes default values
diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
index 0e0f36c606..9e28be8a3a 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
@@ -6,8 +6,8 @@ import java.security.InvalidKeyException
import java.security.PublicKey
import java.security.SignatureException
-// TODO: Is there a use-case for bare [DigitalSignature], or is everything a [DigitalSignature.WithKey]? If there's no
-// actual use-case, we should merge the with key version into the parent class. In that case [CompositeSignatureWithKeys]
+// TODO: Is there a use-case for bare DigitalSignature, or is everything a DigitalSignature.WithKey? If there's no
+// actual use-case, we should merge the with key version into the parent class. In that case CompositeSignatureWithKeys
// should be renamed to match.
/** A wrapper around a digital signature. */
@CordaSerializable
diff --git a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt
index 831e20758e..1555ed70fc 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt
@@ -3,7 +3,7 @@ package net.corda.core.crypto
import java.util.*
/**
- * Creation and verification of a Merkle Tree for a Wire Transaction.
+ * Creation and verification of a Merkle tree for a [WireTransaction].
*
* See: https://en.wikipedia.org/wiki/Merkle_tree
*
@@ -49,7 +49,7 @@ sealed class MerkleTree {
*/
private tailrec fun buildMerkleTree(lastNodesList: List): MerkleTree {
if (lastNodesList.size == 1) {
- return lastNodesList[0] //Root reached.
+ return lastNodesList[0] // Root reached.
} else {
val newLevelHashes: MutableList = ArrayList()
val n = lastNodesList.size
diff --git a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
new file mode 100644
index 0000000000..e5533895e5
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
@@ -0,0 +1,22 @@
+package net.corda.core.crypto
+
+import net.corda.core.identity.AnonymousParty
+import net.corda.core.serialization.CordaSerializable
+import java.security.PublicKey
+
+object NullKeys {
+ @CordaSerializable
+ object NullPublicKey : PublicKey, Comparable {
+ override fun getAlgorithm() = "NULL"
+ override fun getEncoded() = byteArrayOf(0)
+ override fun getFormat() = "NULL"
+ override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
+ override fun toString() = "NULL_KEY"
+ }
+
+ val NULL_PARTY = AnonymousParty(NullPublicKey)
+
+ /** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
+ val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
index af2114ab6d..837f27faea 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
@@ -12,7 +12,7 @@ import java.security.MessageDigest
*/
@CordaSerializable
sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
- /** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes) */
+ /** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes). */
class SHA256(bytes: ByteArray) : SecureHash(bytes) {
init {
require(bytes.size == 32)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt b/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt
index a893381f7b..5027247980 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt
@@ -11,4 +11,3 @@ import net.corda.core.serialization.CordaSerializable
*/
@CordaSerializable
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)
-
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
index 9ec9d26863..0176397c2f 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
@@ -29,4 +29,5 @@ data class SignatureScheme(
val signatureName: String,
val algSpec: AlgorithmParameterSpec?,
val keySize: Int?,
- val desc: String)
+ val desc: String
+)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/X500NameUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/X500NameUtils.kt
deleted file mode 100644
index 6edd82ca93..0000000000
--- a/core/src/main/kotlin/net/corda/core/crypto/X500NameUtils.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-@file:JvmName("X500NameUtils")
-package net.corda.core.crypto
-
-import net.corda.core.internal.toX509CertHolder
-import org.bouncycastle.asn1.ASN1Encodable
-import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.asn1.x500.X500NameBuilder
-import org.bouncycastle.asn1.x500.style.BCStyle
-import org.bouncycastle.cert.X509CertificateHolder
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
-import java.security.KeyPair
-import java.security.cert.X509Certificate
-
-/**
- * Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
- * @throws IllegalArgumentException if the distinguished name does not contain a common name element.
- */
-fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
-
-/**
- * Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
- * adds one.
- * @throws IllegalArgumentException if the distinguished name does not contain a common name element.
- */
-fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
-
-/**
- * Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
- *
- * @param mutator a function to generate the new value from the previous one.
- * @throws IllegalArgumentException if the distinguished name does not contain a common name element.
- */
-private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
- val builder = X500NameBuilder(BCStyle.INSTANCE)
- var matched = false
- this.rdNs.forEach { rdn ->
- rdn.typesAndValues.forEach { typeAndValue ->
- when (typeAndValue.type) {
- BCStyle.CN -> {
- matched = true
- builder.addRDN(typeAndValue.type, mutator(typeAndValue.value))
- }
- else -> {
- builder.addRDN(typeAndValue)
- }
- }
- }
- }
- require(matched) { "Input X.500 name must include a common name (CN) attribute: ${this}" }
- return builder.build()
-}
-
-val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
-val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
-val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
-val X500Name.locationOrNull: String? get() = try {
- location
-} catch (e: Exception) {
- null
-}
-val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
-val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
-
-/**
- * Generate a distinguished name from the provided values.
- */
-@JvmOverloads
-fun getX509Name(myLegalName: String, nearestCity: String, email: String, country: String? = null): X500Name {
- return X500NameBuilder(BCStyle.INSTANCE).let { builder ->
- builder.addRDN(BCStyle.CN, myLegalName)
- builder.addRDN(BCStyle.L, nearestCity)
- country?.let { builder.addRDN(BCStyle.C, it) }
- builder.addRDN(BCStyle.E, email)
- builder.build()
- }
-}
-
-data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/testing/NullKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/testing/NullKeys.kt
deleted file mode 100644
index de2bc3ee42..0000000000
--- a/core/src/main/kotlin/net/corda/core/crypto/testing/NullKeys.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package net.corda.core.crypto.testing
-
-import net.corda.core.crypto.SignatureMetadata
-import net.corda.core.crypto.TransactionSignature
-import net.corda.core.identity.AnonymousParty
-import net.corda.core.serialization.CordaSerializable
-import java.security.PublicKey
-
-@CordaSerializable
-object NullPublicKey : PublicKey, Comparable {
- override fun getAlgorithm() = "NULL"
- override fun getEncoded() = byteArrayOf(0)
- override fun getFormat() = "NULL"
- override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
- override fun toString() = "NULL_KEY"
-}
-
-val NULL_PARTY = AnonymousParty(NullPublicKey)
-
-/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
-val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt
index c4a84da857..26e8b79874 100644
--- a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt
@@ -138,6 +138,7 @@ abstract class AbstractStateReplacementFlow {
// We use Void? instead of Unit? as that's what you'd use in Java.
abstract class Acceptor(val otherSide: Party,
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic() {
+ constructor(otherSide: Party) : this(otherSide, Acceptor.tracker())
companion object {
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
object APPROVING : ProgressTracker.Step("State replacement approved")
diff --git a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt
index 9f95d897a5..723a585cae 100644
--- a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt
@@ -3,7 +3,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
-import net.corda.core.crypto.toBase58String
+import net.corda.core.utilities.toBase58String
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
index d6e7024811..c9a1cbee02 100644
--- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
@@ -1,72 +1,146 @@
package net.corda.core.flows
+import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
+import net.corda.core.identity.Party
import net.corda.core.transactions.LedgerTransaction
+import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
/**
- * A flow to be used for upgrading state objects of an old contract to a new contract.
+ * A flow to be used for authorising and upgrading state objects of an old contract to a new contract.
*
* This assembles the transaction for contract upgrade and sends out change proposals to all participants
* of that state. If participants agree to the proposed change, they each sign the transaction.
* Finally, the transaction containing all signatures is sent back to each participant so they can record it and
* use the new updated state for future transactions.
*/
-@InitiatingFlow
-@StartableByRPC
-class ContractUpgradeFlow(
- originalState: StateAndRef,
- newContractClass: Class>
-) : AbstractStateReplacementFlow.Instigator>>(originalState, newContractClass) {
+object ContractUpgradeFlow {
- companion object {
- @JvmStatic
- fun verify(tx: LedgerTransaction) {
- // Contract Upgrade transaction should have 1 input, 1 output and 1 command.
- verify(
- tx.inputStates.single(),
- tx.outputStates.single(),
- tx.commandsOfType().single())
+ /**
+ * Authorise a contract state upgrade.
+ * This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
+ * Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using the [UpgradedContract] class.
+ * This method will NOT initiate the upgrade process. To start the upgrade process, see [Initiator].
+ */
+ @StartableByRPC
+ class Authorise(
+ val stateAndRef: StateAndRef<*>,
+ private val upgradedContractClass: Class>
+ ) : FlowLogic() {
+ @Suspendable
+ override fun call(): Void? {
+ val upgrade = upgradedContractClass.newInstance()
+ if (upgrade.legacyContract != stateAndRef.state.data.contract.javaClass) {
+ throw FlowException("The contract state cannot be upgraded using provided UpgradedContract.")
+ }
+ serviceHub.contractUpgradeService.storeAuthorisedContractUpgrade(stateAndRef.ref, upgradedContractClass)
+ return null
}
- @JvmStatic
- fun verify(input: ContractState, output: ContractState, commandData: Command) {
- val command = commandData.value
- val participantKeys: Set = input.participants.map { it.owningKey }.toSet()
- val keysThatSigned: Set = commandData.signers.toSet()
- @Suppress("UNCHECKED_CAST")
- val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract
- requireThat {
- "The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
- "Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
- "Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
- "Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
+ }
+
+ /**
+ * Deauthorise a contract state upgrade.
+ * This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
+ */
+ @StartableByRPC
+ class Deauthorise(
+ val stateRef: StateRef
+ ) : FlowLogic< Void?>() {
+ @Suspendable
+ override fun call(): Void? {
+ serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
+ return null
+ }
+ }
+
+ @InitiatingFlow
+ @StartableByRPC
+ class Initiator(
+ originalState: StateAndRef,
+ newContractClass: Class>
+ ) : AbstractStateReplacementFlow.Instigator>>(originalState, newContractClass) {
+
+ companion object {
+ fun assembleBareTx(
+ stateRef: StateAndRef,
+ upgradedContractClass: Class>,
+ privacySalt: PrivacySalt
+ ): TransactionBuilder {
+ val contractUpgrade = upgradedContractClass.newInstance()
+ return TransactionBuilder(stateRef.state.notary)
+ .withItems(
+ stateRef,
+ contractUpgrade.upgrade(stateRef.state.data),
+ Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }),
+ privacySalt
+ )
}
}
- fun assembleBareTx(
- stateRef: StateAndRef,
- upgradedContractClass: Class>,
- privacySalt: PrivacySalt
- ): TransactionBuilder {
- val contractUpgrade = upgradedContractClass.newInstance()
- return TransactionBuilder(stateRef.state.notary)
- .withItems(
- stateRef,
- contractUpgrade.upgrade(stateRef.state.data),
- Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }),
- privacySalt
- )
+ @Suspendable
+ override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
+ val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
+ val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
+ // TODO: We need a much faster way of finding our key in the transaction
+ val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
+ val stx = serviceHub.signInitialTransaction(baseTx, myKey)
+ return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
}
}
- override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
- val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
- val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
- // TODO: We need a much faster way of finding our key in the transaction
- val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
- val stx = serviceHub.signInitialTransaction(baseTx, myKey)
- return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
+ @StartableByRPC
+ @InitiatedBy(ContractUpgradeFlow.Initiator::class)
+ class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor>>(otherSide) {
+
+ companion object {
+ @JvmStatic
+ fun verify(tx: LedgerTransaction) {
+ // Contract Upgrade transaction should have 1 input, 1 output and 1 command.
+ verify(tx.inputStates.single(),
+ tx.outputStates.single(),
+ tx.commandsOfType().single())
+ }
+
+ @JvmStatic
+ fun verify(input: ContractState, output: ContractState, commandData: Command) {
+ val command = commandData.value
+ val participantKeys: Set = input.participants.map { it.owningKey }.toSet()
+ val keysThatSigned: Set = commandData.signers.toSet()
+ @Suppress("UNCHECKED_CAST")
+ val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract
+ requireThat {
+ "The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
+ "Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
+ "Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
+ "Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
+ }
+ }
+ }
+
+ @Suspendable
+ @Throws(StateReplacementException::class)
+ override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal>>) {
+ // Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and
+ // verify outputs matches the proposed upgrade.
+ val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
+ requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
+ val oldStateAndRef = ourSTX!!.tx.outRef(proposal.stateRef.index)
+ val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
+ throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
+ val proposedTx = stx.tx
+ val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
+ requireThat {
+ "The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
+ "The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
+ "The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
+ }
+ ContractUpgradeFlow.Acceptor.verify(
+ oldStateAndRef.state.data,
+ expectedTx.outRef(0).state.data,
+ expectedTx.toLedgerTransaction(serviceHub).commandsOfType().single())
+ }
}
}
diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt
index 584692bd3d..74d6b2562c 100644
--- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt
@@ -72,7 +72,7 @@ object NotaryFlow {
val tx: Any = if (stx.isNotaryChangeTransaction()) {
stx.notaryChangeTx
} else {
- stx.tx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
+ stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
}
sendAndReceiveWithRetry(notaryParty, tx)
}
diff --git a/core/src/main/kotlin/net/corda/core/identity/Party.kt b/core/src/main/kotlin/net/corda/core/identity/Party.kt
index 5652fc2688..f25d6446d1 100644
--- a/core/src/main/kotlin/net/corda/core/identity/Party.kt
+++ b/core/src/main/kotlin/net/corda/core/identity/Party.kt
@@ -2,7 +2,7 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.Crypto
-import net.corda.core.crypto.composite.CompositeKey
+import net.corda.core.crypto.CompositeKey
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt
new file mode 100644
index 0000000000..2f402f43e5
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt
@@ -0,0 +1,69 @@
+package net.corda.core.internal
+
+import net.corda.core.contracts.Attachment
+import net.corda.core.crypto.SecureHash
+import net.corda.core.identity.Party
+import net.corda.core.serialization.MissingAttachmentsException
+import net.corda.core.serialization.SerializeAsTokenContext
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.security.CodeSigner
+import java.util.jar.JarInputStream
+
+abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
+ companion object {
+ fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
+ return {
+ val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
+ (a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
+ }
+ }
+
+ /** @see */
+ private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex()
+ private val shredder = ByteArray(1024)
+ }
+
+ protected val attachmentData: ByteArray by lazy(dataLoader)
+ override fun open(): InputStream = attachmentData.inputStream()
+ override val signers by lazy {
+ // Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":
+ var attachmentSigners: MutableSet? = null
+ openAsJAR().use { jar ->
+ while (true) {
+ val entry = jar.nextJarEntry ?: break
+ if (entry.isDirectory || unsignableEntryName.matches(entry.name)) continue
+ while (jar.read(shredder) != -1) { // Must read entry fully for codeSigners to be valid.
+ // Do nothing.
+ }
+ val entrySigners = entry.codeSigners ?: emptyArray()
+ attachmentSigners?.retainAll(entrySigners) ?: run { attachmentSigners = entrySigners.toMutableSet() }
+ if (attachmentSigners!!.isEmpty()) break // Performance short-circuit.
+ }
+ }
+ (attachmentSigners ?: emptySet()).map {
+ Party(it.signerCertPath.certificates[0].toX509CertHolder())
+ }.sortedBy { it.name.toString() } // Determinism.
+ }
+
+ override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
+ override fun hashCode() = id.hashCode()
+ override fun toString() = "${javaClass.simpleName}(id=$id)"
+}
+
+@Throws(IOException::class)
+fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
+ fun String.norm() = toLowerCase().split('\\', '/') // XXX: Should this really be locale-sensitive?
+ val p = path.norm()
+ while (true) {
+ val e = nextJarEntry ?: break
+ if (!e.isDirectory && e.name.norm() == p) {
+ copyTo(outputTo)
+ return
+ }
+ closeEntry()
+ }
+ throw FileNotFoundException(path)
+}
diff --git a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt
index 5006843121..ce95ee9838 100644
--- a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt
@@ -1,7 +1,6 @@
package net.corda.core.internal
import co.paralleluniverse.fibers.Suspendable
-import net.corda.core.contracts.AbstractAttachment
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.NamedByHash
import net.corda.core.crypto.SecureHash
diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
index 3133e1c3b0..6470493af9 100644
--- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
+++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
@@ -123,11 +123,11 @@ interface CordaRPCOps : RPCOps {
*
* Generic vault query function which takes a [QueryCriteria] object to define filters,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
- * and returns a [Vault.PageAndUpdates] object containing
- * 1) a snapshot as a [Vault.Page] (described previously in [queryBy])
+ * and returns a [DataFeed] object containing
+ * 1) a snapshot as a [Vault.Page] (described previously in [CordaRPCOps.vaultQueryBy])
* 2) an [Observable] of [Vault.Update]
*
- * Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
+ * Notes: the snapshot part of the query adheres to the same behaviour as the [CordaRPCOps.vaultQueryBy] function.
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
*/
// DOCSTART VaultTrackByAPI
@@ -157,15 +157,21 @@ interface CordaRPCOps : RPCOps {
// DOCEND VaultTrackAPIHelpers
/**
- * Returns a list of all recorded transactions.
+ * @suppress Returns a list of all recorded transactions.
+ *
+ * TODO This method should be removed once SGX work is finalised and the design of the corresponding API using [FilteredTransaction] can be started
*/
- fun verifiedTransactionsSnapshot(): List
+ @Deprecated("This method is intended only for internal use and will be removed from the public API soon.")
+ fun internalVerifiedTransactionsSnapshot(): List
/**
- * Returns a data feed of all recorded transactions and an observable of future recorded ones.
+ * @suppress Returns a data feed of all recorded transactions and an observable of future recorded ones.
+ *
+ * TODO This method should be removed once SGX work is finalised and the design of the corresponding API using [FilteredTransaction] can be started
*/
+ @Deprecated("This method is intended only for internal use and will be removed from the public API soon.")
@RPCReturnsObservables
- fun verifiedTransactionsFeed(): DataFeed, SignedTransaction>
+ fun internalVerifiedTransactionsFeed(): DataFeed, SignedTransaction>
/**
* Returns a snapshot list of existing state machine id - recorded transaction hash mappings.
@@ -194,14 +200,14 @@ interface CordaRPCOps : RPCOps {
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
*/
@RPCReturnsObservables
- fun startFlowDynamic(logicType: Class>, vararg args: Any?): FlowHandle
+ fun startFlowDynamic(logicType: Class>, vararg args: Any?): FlowHandle
/**
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
* result of running the flow. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
*/
@RPCReturnsObservables
- fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle
+ fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle
/**
* Returns Node's identity, assuming this will not change while the node is running.
@@ -233,20 +239,6 @@ interface CordaRPCOps : RPCOps {
*/
fun uploadAttachment(jar: InputStream): SecureHash
- /**
- * Authorise a contract state upgrade.
- * This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
- * Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
- * This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
- */
- fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class>)
-
- /**
- * Authorise a contract state upgrade.
- * This will remove the upgrade authorisation from the vault.
- */
- fun deauthoriseContractUpgrade(state: StateAndRef<*>)
-
/**
* Returns the node's current time.
*/
@@ -257,7 +249,7 @@ interface CordaRPCOps : RPCOps {
* complete with an exception if it is unable to.
*/
@RPCReturnsObservables
- fun waitUntilRegisteredWithNetworkMap(): CordaFuture
+ fun waitUntilNetworkReady(): CordaFuture
// TODO These need rethinking. Instead of these direct calls we should have a way of replicating a subset of
// the node's state locally and query that directly.
@@ -299,6 +291,11 @@ interface CordaRPCOps : RPCOps {
* @return the node info if available.
*/
fun nodeIdentityFromParty(party: AbstractParty): NodeInfo?
+
+ /**
+ * Clear all network map data from local node cache.
+ */
+ fun clearNetworkMapCache()
}
inline fun CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
@@ -322,25 +319,25 @@ inline fun CordaRPCOps.vaultTrackBy(criteria: QueryC
* Note that the passed in constructor function is only used for unification of other type parameters and reification of
* the Class instance of the flow. This could be changed to use the constructor function directly.
*/
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: () -> R
): FlowHandle = startFlowDynamic(R::class.java)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A) -> R,
arg0: A
): FlowHandle = startFlowDynamic(R::class.java, arg0)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B) -> R,
arg0: A,
arg1: B
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C) -> R,
arg0: A,
@@ -348,7 +345,7 @@ inline fun > CordaRPCOps.startFlow(
arg2: C
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C, D) -> R,
arg0: A,
@@ -357,7 +354,7 @@ inline fun > CordaRPCOps.startFlow
arg3: D
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C, D, E) -> R,
arg0: A,
@@ -367,7 +364,7 @@ inline fun > CordaRPCOps.startF
arg4: E
): FlowHandle = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
-inline fun > CordaRPCOps.startFlow(
+inline fun > CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B, C, D, E, F) -> R,
arg0: A,
@@ -382,20 +379,20 @@ inline fun > CordaRPCOps.sta
* Same again, except this time with progress-tracking enabled.
*/
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: () -> R
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A) -> R,
arg0: A
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B) -> R,
arg0: A,
@@ -403,7 +400,7 @@ inline fun > CordaRPCOps.startTrackedFlo
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C) -> R,
arg0: A,
@@ -412,7 +409,7 @@ inline fun > CordaRPCOps.startTracked
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C, D) -> R,
arg0: A,
@@ -422,7 +419,7 @@ inline fun > CordaRPCOps.startTrac
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C, D, E) -> R,
arg0: A,
@@ -433,7 +430,7 @@ inline fun > CordaRPCOps.startT
): FlowProgressHandle = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
@Suppress("unused")
-inline fun > CordaRPCOps.startTrackedFlow(
+inline fun > CordaRPCOps.startTrackedFlow(
@Suppress("unused_parameter")
flowConstructor: (A, B, C, D, E, F) -> R,
arg0: A,
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 d7f30a4f50..322fcf3d5a 100644
--- a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt
+++ b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt
@@ -6,7 +6,7 @@ import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.NetworkHostAndPort
-import net.corda.core.utilities.NonEmptySet
+import net.corda.core.utilities.locality
/**
* Information for an advertised service including the service specific identity information.
@@ -21,11 +21,13 @@ data class ServiceEntry(val info: ServiceInfo, val identity: PartyAndCertificate
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
@CordaSerializable
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: NonEmptySet,
+ // TODO After removing of services these two fields will be merged together and made NonEmptySet.
+ val legalIdentityAndCert: PartyAndCertificate,
+ val legalIdentitiesAndCerts: Set,
val platformVersion: Int,
val advertisedServices: List = emptyList(),
- val worldMapLocation: WorldMapLocation? = null) {
+ val serial: Long
+) {
init {
require(advertisedServices.none { it.identity == legalIdentityAndCert }) {
"Service identities must be different from node legal identity"
@@ -37,4 +39,12 @@ data class NodeInfo(val addresses: List,
fun serviceIdentities(type: ServiceType): List {
return advertisedServices.mapNotNull { if (it.info.type.isSubTypeOf(type)) it.identity.party else null }
}
+
+ /**
+ * Uses node's owner X500 name to infer the node's location. Used in Explorer in map view.
+ */
+ fun getWorldMapLocation(): WorldMapLocation? {
+ val nodeOwnerLocation = legalIdentity.name.locality
+ return nodeOwnerLocation.let { CityDatabase[it] }
+ }
}
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 fb882eb4ad..695e32a0a4 100644
--- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
+++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
@@ -7,6 +7,7 @@ import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
+import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
@@ -46,6 +47,7 @@ interface ServiceHub : ServicesForResolution {
val vaultService: VaultService
val vaultQueryService: VaultQueryService
val keyManagementService: KeyManagementService
+ val contractUpgradeService: ContractUpgradeService
/**
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
@@ -170,7 +172,7 @@ interface ServiceHub : ServicesForResolution {
/**
* Helper method to construct an initial partially signed transaction from a TransactionBuilder
- * using the default identity key contained in the node. The legal Indentity key is used to sign.
+ * using the default identity key contained in the node. The legal identity key is used to sign.
* @param builder The TransactionBuilder to seal with the node's signature.
* Any existing signatures on the builder will be preserved.
* @return Returns a SignedTransaction with the new node signature attached.
@@ -202,7 +204,9 @@ interface ServiceHub : ServicesForResolution {
}
/**
- * Helper method to create an additional signature for an existing (partially) [SignedTransaction].
+ * Helper method to create an additional signature for an existing (partially) [SignedTransaction]. Additional
+ * [SignatureMetadata], including the
+ * platform version used during signing and the cryptographic signature scheme use, is added to the signature.
* @param signedTransaction The [SignedTransaction] to which the signature will apply.
* @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
@@ -213,10 +217,12 @@ interface ServiceHub : ServicesForResolution {
createSignature(signedTransaction, publicKey, SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(publicKey).schemeNumberID))
/**
- * Helper method to create an additional signature for an existing (partially) [SignedTransaction]
- * using the default identity signing key of the node. The legal identity key is used to sign.
+ * Helper method to create a signature for an existing (partially) [SignedTransaction]
+ * using the default identity signing key of the node. The legal identity key is used to sign. Additional
+ * [SignatureMetadata], including the
+ * platform version used during signing and the cryptographic signature scheme use, is added to the signature.
* @param signedTransaction The SignedTransaction to which the signature will apply.
- * @return The DigitalSignature.WithKey generated by signing with the internally held identity PrivateKey.
+ * @return the TransactionSignature generated by signing with the internally held identity PrivateKey.
*/
fun createSignature(signedTransaction: SignedTransaction): TransactionSignature {
return createSignature(signedTransaction, legalIdentityKey)
@@ -242,6 +248,36 @@ interface ServiceHub : ServicesForResolution {
*/
fun addSignature(signedTransaction: SignedTransaction): SignedTransaction = addSignature(signedTransaction, legalIdentityKey)
+ // Helper method to create a signature for a FilteredTransaction.
+ private fun createSignature(filteredTransaction: FilteredTransaction, publicKey: PublicKey, signatureMetadata: SignatureMetadata): TransactionSignature {
+ val signableData = SignableData(filteredTransaction.id, signatureMetadata)
+ return keyManagementService.sign(signableData, publicKey)
+ }
+
+ /**
+ * Helper method to create a signature for a FilteredTransaction. Additional [SignatureMetadata], including the
+ * platform version used during signing and the cryptographic signature scheme use, is added to the signature.
+ * @param filteredTransaction the [FilteredTransaction] to which the signature will apply.
+ * @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 [TransactionSignature] generated by signing with the internally held [java.security.PrivateKey].
+ */
+ fun createSignature(filteredTransaction: FilteredTransaction, publicKey: PublicKey) =
+ createSignature(filteredTransaction, publicKey, SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(publicKey).schemeNumberID))
+
+ /**
+ * Helper method to create a signature for a FilteredTransaction
+ * using the default identity signing key of the node. The legal identity key is used to sign. Additional
+ * [SignatureMetadata], including the platform version used during signing and the cryptographic signature scheme use,
+ * is added to the signature.
+ * @param filteredTransaction the FilteredTransaction to which the signature will apply.
+ * @return the [TransactionSignature] generated by signing with the internally held identity [java.security.PrivateKey].
+ */
+ fun createSignature(filteredTransaction: FilteredTransaction): TransactionSignature {
+ return createSignature(filteredTransaction, legalIdentityKey)
+ }
+
/**
* Exposes a JDBC connection (session) object using the currently configured database.
* Applications can use this to execute arbitrary SQL queries (native, direct, prepared, callable)
diff --git a/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt
new file mode 100644
index 0000000000..42d1689796
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt
@@ -0,0 +1,22 @@
+package net.corda.core.node.services
+
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.UpgradedContract
+import net.corda.core.flows.ContractUpgradeFlow
+
+/**
+ * The [ContractUpgradeService] is responsible for securely upgrading contract state objects according to
+ * a specified and mutually agreed (amongst participants) contract version.
+ * See also [ContractUpgradeFlow] to understand the workflow associated with contract upgrades.
+ */
+interface ContractUpgradeService {
+
+ /** Get contracts we would be willing to upgrade the suggested contract to. */
+ fun getAuthorisedContractUpgrade(ref: StateRef): String?
+
+ /** Store authorised state ref and associated UpgradeContract class */
+ fun storeAuthorisedContractUpgrade(ref: StateRef, upgradedContractClass: Class>)
+
+ /** Remove a previously authorised state ref */
+ fun removeAuthorisedContractUpgrade(ref: StateRef)
+}
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 33fbd1f10e..3f854a44fc 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
@@ -8,6 +8,7 @@ import net.corda.core.internal.randomOrNull
import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable
+import net.corda.core.utilities.NetworkHostAndPort
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable
import java.security.PublicKey
@@ -44,7 +45,7 @@ interface NetworkMapCache {
/** Tracks changes to the network map cache */
val changed: Observable
/** Future to track completion of the NetworkMapService registration. */
- val mapServiceRegistered: CordaFuture
+ val nodeReady: CordaFuture
/**
* Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the
@@ -76,7 +77,10 @@ interface NetworkMapCache {
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 }
+ fun getNodeByLegalName(principal: X500Name): NodeInfo?
+
+ /** Look up the node info for a host and port. */
+ fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
/**
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
@@ -144,4 +148,9 @@ interface NetworkMapCache {
"Your options are: ${notaryNodes.map { "\"${it.notaryIdentity.name}\"" }.joinToString()}.")
return notary.advertisedServices.any { it.info.type.isValidatingNotary() }
}
+
+ /**
+ * Clear all network map data from local node cache.
+ */
+ fun clearNetworkMapCache()
}
diff --git a/core/src/main/kotlin/net/corda/core/node/services/ServiceType.kt b/core/src/main/kotlin/net/corda/core/node/services/ServiceType.kt
index 80111a3cdb..97d2888e88 100644
--- a/core/src/main/kotlin/net/corda/core/node/services/ServiceType.kt
+++ b/core/src/main/kotlin/net/corda/core/node/services/ServiceType.kt
@@ -30,6 +30,7 @@ class ServiceType private constructor(val id: String) {
val regulator: ServiceType = corda.getSubType("regulator")
val networkMap: ServiceType = corda.getSubType("network_map")
+ @JvmStatic
fun getServiceType(namespace: String, typeId: String): ServiceType {
require(!namespace.startsWith("corda")) { "Corda namespace is protected" }
return baseWithSubType(namespace, typeId)
diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt
index ebd70ae93d..0b7aeb862f 100644
--- a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt
+++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt
@@ -173,17 +173,6 @@ interface VaultService {
*/
val updatesPublisher: PublishSubject>
- /**
- * Possibly update the vault by marking as spent states that these transactions consume, and adding any relevant
- * new states that they create. You should only insert transactions that have been successfully verified here!
- *
- * TODO: Consider if there's a good way to enforce the must-be-verified requirement in the type system.
- */
- fun notifyAll(txns: Iterable)
-
- /** Same as notifyAll but with a single transaction. */
- fun notify(tx: CoreTransaction) = notifyAll(listOf(tx))
-
/**
* Provide a [CordaFuture] for when a [StateRef] is consumed, which can be very useful in building tests.
*/
@@ -191,24 +180,6 @@ interface VaultService {
return updates.filter { it.consumed.any { it.ref == ref } }.toFuture()
}
- /** Get contracts we would be willing to upgrade the suggested contract to. */
- // TODO: We need a better place to put business logic functions
- fun getAuthorisedContractUpgrade(ref: StateRef): Class>?
-
- /**
- * Authorise a contract state upgrade.
- * This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
- * Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
- * This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
- */
- fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class>)
-
- /**
- * Authorise a contract state upgrade.
- * This will remove the upgrade authorisation from the vault.
- */
- fun deauthoriseContractUpgrade(stateAndRef: StateAndRef<*>)
-
/**
* Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id
* Multiple notes may be attached to the same [LedgerTransaction].
diff --git a/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt b/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt
index 5a55d9cc92..0f9d985791 100644
--- a/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt
+++ b/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt
@@ -4,7 +4,6 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.FungibleAsset
import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.UniqueIdentifier
-import net.corda.core.crypto.toBase58String
import net.corda.core.identity.AbstractParty
import java.util.*
import javax.persistence.*
diff --git a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
new file mode 100644
index 0000000000..df8016115a
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
@@ -0,0 +1,125 @@
+package net.corda.core.schemas
+
+import net.corda.core.identity.PartyAndCertificate
+import net.corda.core.node.NodeInfo
+import net.corda.core.node.ServiceEntry
+import net.corda.core.serialization.deserialize
+import net.corda.core.serialization.serialize
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.toBase58String
+import java.io.Serializable
+import javax.persistence.*
+
+object NodeInfoSchema
+
+object NodeInfoSchemaV1 : MappedSchema(
+ schemaFamily = NodeInfoSchema.javaClass,
+ version = 1,
+ mappedTypes = listOf(PersistentNodeInfo::class.java, DBPartyAndCertificate::class.java, DBHostAndPort::class.java)
+) {
+ @Entity
+ @Table(name = "node_infos")
+ class PersistentNodeInfo(
+ @Id
+ @GeneratedValue
+ @Column(name = "node_info_id")
+ var id: Int,
+
+ @Column(name = "addresses")
+ @OneToMany(cascade = arrayOf(CascadeType.ALL), orphanRemoval = true)
+ val addresses: List,
+
+ @Column(name = "legal_identities_certs")
+ @ManyToMany(cascade = arrayOf(CascadeType.ALL))
+ @JoinTable(name = "link_nodeinfo_party",
+ joinColumns = arrayOf(JoinColumn(name="node_info_id")),
+ inverseJoinColumns = arrayOf(JoinColumn(name="party_name")))
+ val legalIdentitiesAndCerts: Set,
+
+ @Column(name = "platform_version")
+ val platformVersion: Int,
+
+ @Column(name = "advertised_services")
+ @ElementCollection
+ var advertisedServices: List = emptyList(),
+
+ /**
+ * serial is an increasing value which represents the version of [NodeInfo].
+ * Not expected to be sequential, but later versions of the registration must have higher values
+ * Similar to the serial number on DNS records.
+ */
+ @Column(name = "serial")
+ val serial: Long
+ ) {
+ fun toNodeInfo(): NodeInfo {
+ return NodeInfo(
+ this.addresses.map { it.toHostAndPort() },
+ this.legalIdentitiesAndCerts.filter { it.isMain }.single().toLegalIdentityAndCert(), // TODO Workaround, it will be changed after PR with services removal.
+ this.legalIdentitiesAndCerts.filter { !it.isMain }.map { it.toLegalIdentityAndCert() }.toSet(),
+ this.platformVersion,
+ this.advertisedServices.map {
+ it.serviceEntry?.deserialize() ?: throw IllegalStateException("Service entry shouldn't be null")
+ },
+ this.serial
+ )
+ }
+ }
+
+ @Embeddable
+ data class PKHostAndPort(
+ val host: String? = null,
+ val port: Int? = null
+ ) : Serializable
+
+ @Entity
+ data class DBHostAndPort(
+ @EmbeddedId
+ private val pk: PKHostAndPort
+ ) {
+ companion object {
+ fun fromHostAndPort(hostAndPort: NetworkHostAndPort) = DBHostAndPort(
+ PKHostAndPort(hostAndPort.host, hostAndPort.port)
+ )
+ }
+ fun toHostAndPort(): NetworkHostAndPort {
+ return NetworkHostAndPort(this.pk.host!!, this.pk.port!!)
+ }
+ }
+
+ @Embeddable // TODO To be removed with services.
+ data class DBServiceEntry(
+ @Column(length = 65535)
+ val serviceEntry: ByteArray? = null
+ )
+
+ /**
+ * PartyAndCertificate entity (to be replaced by referencing final Identity Schema).
+ */
+ @Entity
+ @Table(name = "node_info_party_cert")
+ data class DBPartyAndCertificate(
+ @Id
+ @Column(name = "owning_key", length = 65535, nullable = false)
+ val owningKey: String,
+
+ //@Id // TODO Do we assume that names are unique? Note: We can't have it as Id, because our toString on X500 is inconsistent.
+ @Column(name = "party_name", nullable = false)
+ val name: String,
+
+ @Column(name = "party_cert_binary")
+ @Lob
+ val partyCertBinary: ByteArray,
+
+ val isMain: Boolean,
+
+ @ManyToMany(mappedBy = "legalIdentitiesAndCerts", cascade = arrayOf(CascadeType.ALL)) // ManyToMany because of distributed services.
+ private val persistentNodeInfos: Set = emptySet()
+ ) {
+ constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false)
+ : this(partyAndCert.party.owningKey.toBase58String(), partyAndCert.party.name.toString(), partyAndCert.serialize().bytes, isMain)
+
+ fun toLegalIdentityAndCert(): PartyAndCertificate {
+ return partyCertBinary.deserialize()
+ }
+ }
+}
diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt
index b9f2094394..9440851fdd 100644
--- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt
+++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt
@@ -3,8 +3,6 @@ package net.corda.core.serialization
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.WriteOnceProperty
-import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT
-import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.sequence
@@ -13,7 +11,7 @@ import net.corda.core.utilities.sequence
* An abstraction for serializing and deserializing objects, with support for versioning of the wire format via
* a header / prefix in the bytes.
*/
-interface SerializationFactory {
+abstract class SerializationFactory {
/**
* Deserialize the bytes in to an object, using the prefixed bytes to determine the format.
*
@@ -21,7 +19,7 @@ interface SerializationFactory {
* @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown.
* @param context A context that configures various parameters to deserialization.
*/
- fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T
+ abstract fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T
/**
* Serialize an object to bytes using the preferred serialization format version from the context.
@@ -29,7 +27,63 @@ interface SerializationFactory {
* @param obj The object to be serialized.
* @param context A context that configures various parameters to serialization, including the serialization format version.
*/
- fun serialize(obj: T, context: SerializationContext): SerializedBytes
+ abstract fun serialize(obj: T, context: SerializationContext): SerializedBytes
+
+ /**
+ * If there is a need to nest serialization/deserialization with a modified context during serialization or deserialization,
+ * this will return the current context used to start serialization/deserialization.
+ */
+ val currentContext: SerializationContext? get() = _currentContext.get()
+
+ /**
+ * A context to use as a default if you do not require a specially configured context. It will be the current context
+ * if the use is somehow nested (see [currentContext]).
+ */
+ val defaultContext: SerializationContext get() = currentContext ?: SerializationDefaults.P2P_CONTEXT
+
+ private val _currentContext = ThreadLocal()
+
+ /**
+ * Change the current context inside the block to that supplied.
+ */
+ fun withCurrentContext(context: SerializationContext?, block: () -> T): T {
+ val priorContext = _currentContext.get()
+ if (context != null) _currentContext.set(context)
+ try {
+ return block()
+ } finally {
+ if (context != null) _currentContext.set(priorContext)
+ }
+ }
+
+ /**
+ * Allow subclasses to temporarily mark themselves as the current factory for the current thread during serialization/deserialization.
+ * Will restore the prior context on exiting the block.
+ */
+ protected fun asCurrent(block: SerializationFactory.() -> T): T {
+ val priorContext = _currentFactory.get()
+ _currentFactory.set(this)
+ try {
+ return block()
+ } finally {
+ _currentFactory.set(priorContext)
+ }
+ }
+
+ companion object {
+ private val _currentFactory = ThreadLocal()
+
+ /**
+ * A default factory for serialization/deserialization, taking into account the [currentFactory] if set.
+ */
+ val defaultFactory: SerializationFactory get() = currentFactory ?: SerializationDefaults.SERIALIZATION_FACTORY
+
+ /**
+ * If there is a need to nest serialization/deserialization with a modified context during serialization or deserialization,
+ * this will return the current factory used to start serialization/deserialization.
+ */
+ val currentFactory: SerializationFactory? get() = _currentFactory.get()
+ }
}
/**
@@ -76,6 +130,12 @@ interface SerializationContext {
*/
fun withClassLoader(classLoader: ClassLoader): SerializationContext
+ /**
+ * Helper method to return a new context based on this context with the appropriate class loader constructed from the passed attachment identifiers.
+ * (Requires the attachment storage to have been enabled).
+ */
+ fun withAttachmentsClassLoader(attachmentHashes: List): SerializationContext
+
/**
* Helper method to return a new context based on this context with the given class specifically whitelisted.
*/
@@ -107,26 +167,26 @@ object SerializationDefaults {
/**
* Convenience extension method for deserializing a ByteSequence, utilising the defaults.
*/
-inline fun ByteSequence.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T {
+inline fun ByteSequence.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T {
return serializationFactory.deserialize(this, T::class.java, context)
}
/**
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
*/
-inline fun SerializedBytes.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T {
+inline fun SerializedBytes.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T {
return serializationFactory.deserialize(this, T::class.java, context)
}
/**
* Convenience extension method for deserializing a ByteArray, utilising the defaults.
*/
-inline fun ByteArray.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T = this.sequence().deserialize(serializationFactory, context)
+inline fun ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.sequence().deserialize(serializationFactory, context)
/**
* Convenience extension method for serializing an object of type T, utilising the defaults.
*/
-fun T.serialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): SerializedBytes {
+fun T.serialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): SerializedBytes {
return serializationFactory.serialize(this, context)
}
@@ -142,4 +202,4 @@ class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) {
interface ClassWhitelist {
fun hasListed(type: Class<*>): Boolean
-}
\ No newline at end of file
+}
diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomization.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomization.kt
index e051f732c3..91c5dea265 100644
--- a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomization.kt
+++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomization.kt
@@ -1,6 +1,6 @@
package net.corda.core.serialization
interface SerializationCustomization {
- fun addToWhitelist(type: Class<*>)
+ fun addToWhitelist(vararg types: Class<*>)
}
diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt
index 276415b041..da0d98dbe7 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt
@@ -27,8 +27,8 @@ abstract class BaseTransaction : NamedByHash {
}
private fun checkNotarySetIfInputsPresent() {
- if (notary == null) {
- check(inputs.isEmpty()) { "The notary must be specified explicitly for any transaction that has inputs" }
+ if (inputs.isNotEmpty()) {
+ check(notary != null) { "The notary must be specified explicitly for any transaction that has inputs" }
}
}
diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
index 59d3e8515d..5c4eb7fa2b 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
@@ -14,7 +14,7 @@ import java.util.function.Predicate
* - Downloading and locally storing all the dependencies of the transaction.
* - Resolving the input states and loading them into memory.
* - Doing some basic key lookups on the [Command]s to see if any keys are from a recognised party, thus converting the
- * [Command] objects into [AuthenticatedObject].
+ * [Command] objects into [CommandWithParties].
* - Deserialising the output states.
*
* All the above refer to inputs using a (txhash, output index) pair.
@@ -28,7 +28,7 @@ data class LedgerTransaction(
override val inputs: List>,
override val outputs: List>,
/** Arbitrary data passed to the program of each input state. */
- val commands: List>,
+ val commands: List>,
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List,
/** The hash of the original serialised WireTransaction. */
diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt
index 38b418e877..b23800baec 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt
@@ -4,7 +4,7 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT
+import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.serialize
import java.nio.ByteBuffer
import java.util.function.Predicate
@@ -22,12 +22,12 @@ fun serializedHash(x: T, privacySalt: PrivacySalt?, index: Int): Secur
fun serializedHash(x: T, nonce: SecureHash): SecureHash {
return if (x !is PrivacySalt) // PrivacySalt is not required to have an accompanied nonce.
- (x.serialize(context = P2P_CONTEXT.withoutReferences()).bytes + nonce.bytes).sha256()
+ (x.serialize(context = SerializationFactory.defaultFactory.defaultContext.withoutReferences()).bytes + nonce.bytes).sha256()
else
serializedHash(x)
}
-fun serializedHash(x: T): SecureHash = x.serialize(context = P2P_CONTEXT.withoutReferences()).bytes.sha256()
+fun serializedHash(x: T): SecureHash = x.serialize(context = SerializationFactory.defaultFactory.defaultContext.withoutReferences()).bytes.sha256()
/** The nonce is computed as Hash(privacySalt || index). */
fun computeNonce(privacySalt: PrivacySalt, index: Int) = (privacySalt.bytes + ByteBuffer.allocate(4).putInt(index).array()).sha256()
@@ -139,13 +139,13 @@ class FilteredLeaves(
/**
* Class representing merkleized filtered transaction.
- * @param rootHash Merkle tree root hash.
+ * @param id Merkle tree root hash.
* @param filteredLeaves Leaves included in a filtered transaction.
* @param partialMerkleTree Merkle branch needed to verify filteredLeaves.
*/
@CordaSerializable
class FilteredTransaction private constructor(
- val rootHash: SecureHash,
+ val id: SecureHash,
val filteredLeaves: FilteredLeaves,
val partialMerkleTree: PartialMerkleTree
) {
@@ -159,21 +159,84 @@ class FilteredTransaction private constructor(
fun buildMerkleTransaction(wtx: WireTransaction,
filtering: Predicate
): FilteredTransaction {
- val filteredLeaves = wtx.filterWithFun(filtering)
+ val filteredLeaves = filterWithFun(wtx, filtering)
val merkleTree = wtx.merkleTree
val pmt = PartialMerkleTree.build(merkleTree, filteredLeaves.availableComponentHashes)
return FilteredTransaction(merkleTree.hash, filteredLeaves, pmt)
}
+
+ /**
+ * Construction of partial transaction from WireTransaction based on filtering.
+ * Note that list of nonces to be sent is updated on the fly, based on the index of the filtered tx component.
+ * @param filtering filtering over the whole WireTransaction
+ * @returns FilteredLeaves used in PartialMerkleTree calculation and verification.
+ */
+ private fun filterWithFun(wtx: WireTransaction, filtering: Predicate): FilteredLeaves {
+ val nonces: MutableList = mutableListOf()
+ val offsets = indexOffsets(wtx)
+ fun notNullFalseAndNoncesUpdate(elem: Any?, index: Int): Any? {
+ return if (elem == null || !filtering.test(elem)) {
+ null
+ } else {
+ nonces.add(computeNonce(wtx.privacySalt, index))
+ elem
+ }
+ }
+
+ fun filterAndNoncesUpdate(t: T, index: Int): Boolean {
+ return if (filtering.test(t)) {
+ nonces.add(computeNonce(wtx.privacySalt, index))
+ true
+ } else {
+ false
+ }
+ }
+
+ // TODO: We should have a warning (require) if all leaves (excluding salt) are visible after filtering.
+ // Consider the above after refactoring FilteredTransaction to implement TraversableTransaction,
+ // so that a WireTransaction can be used when required to send a full tx (e.g. RatesFixFlow in Oracles).
+ return FilteredLeaves(
+ wtx.inputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index) },
+ wtx.attachments.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[0]) },
+ wtx.outputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[1]) },
+ wtx.commands.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[2]) },
+ notNullFalseAndNoncesUpdate(wtx.notary, offsets[3]) as Party?,
+ notNullFalseAndNoncesUpdate(wtx.timeWindow, offsets[4]) as TimeWindow?,
+ nonces
+ )
+ }
+
+ // We use index offsets, to get the actual leaf-index per transaction component required for nonce computation.
+ private fun indexOffsets(wtx: WireTransaction): List {
+ // There is no need to add an index offset for inputs, because they are the first components in the
+ // transaction format and it is always zero. Thus, offsets[0] corresponds to attachments,
+ // offsets[1] to outputs, offsets[2] to commands and so on.
+ val offsets = mutableListOf(wtx.inputs.size, wtx.inputs.size + wtx.attachments.size)
+ offsets.add(offsets.last() + wtx.outputs.size)
+ offsets.add(offsets.last() + wtx.commands.size)
+ if (wtx.notary != null) {
+ offsets.add(offsets.last() + 1)
+ } else {
+ offsets.add(offsets.last())
+ }
+ if (wtx.timeWindow != null) {
+ offsets.add(offsets.last() + 1)
+ } else {
+ offsets.add(offsets.last())
+ }
+ // No need to add offset for privacySalt as it doesn't require a nonce.
+ return offsets
+ }
}
/**
- * Runs verification of Partial Merkle Branch against [rootHash].
+ * Runs verification of partial Merkle branch against [id].
*/
@Throws(MerkleTreeException::class)
fun verify(): Boolean {
val hashes: List = filteredLeaves.availableComponentHashes
if (hashes.isEmpty())
throw MerkleTreeException("Transaction without included leaves.")
- return partialMerkleTree.verify(rootHash, hashes)
+ return partialMerkleTree.verify(id, hashes)
}
}
diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt
index 705e5553ca..0148421383 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt
@@ -3,7 +3,7 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
-import net.corda.core.crypto.toBase58String
+import net.corda.core.utilities.toBase58String
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import java.security.PublicKey
diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
index c2afc3f2ec..7da67105f1 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
@@ -14,6 +14,7 @@ import java.security.KeyPair
import java.security.PublicKey
import java.security.SignatureException
import java.util.*
+import java.util.function.Predicate
/**
* SignedTransaction wraps a serialized WireTransaction. It contains one or more signatures, each one for
@@ -29,6 +30,7 @@ import java.util.*
* sign.
*/
// DOCSTART 1
+@CordaSerializable
data class SignedTransaction(val txBits: SerializedBytes,
override val sigs: List
) : TransactionWithSignatures {
@@ -50,12 +52,18 @@ data class SignedTransaction(val txBits: SerializedBytes,
/** The id of the contained [WireTransaction]. */
override val id: SecureHash get() = transaction.id
- /** Returns the contained [WireTransaction], or throws if this is a notary change transaction */
+ /** Returns the contained [WireTransaction], or throws if this is a notary change transaction. */
val tx: WireTransaction get() = transaction as WireTransaction
- /** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction */
+ /** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
val notaryChangeTx: NotaryChangeWireTransaction get() = transaction as NotaryChangeWireTransaction
+ /**
+ * Helper function to directly build a [FilteredTransaction] using provided filtering functions,
+ * without first accessing the [WireTransaction] [tx].
+ */
+ fun buildFilteredTransaction(filtering: Predicate) = tx.buildFilteredTransaction(filtering)
+
/** Helper to access the inputs of the contained transaction */
val inputs: List get() = transaction.inputs
/** Helper to access the notary of the contained transaction */
diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt
index c3492655f1..44a3797f75 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt
@@ -33,8 +33,9 @@ data class WireTransaction(
) : CoreTransaction(), TraversableTransaction {
init {
checkBaseInvariants()
+ check(inputs.isNotEmpty() || outputs.isNotEmpty()) { "A transaction must contain at least one input or output state" }
+ check(commands.isNotEmpty()) { "A transaction must contain at least one command" }
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
- check(availableComponents.isNotEmpty()) { "A WireTransaction cannot be empty" }
}
/** The transaction id is represented by the root hash of Merkle tree over the transaction components. */
@@ -83,7 +84,7 @@ data class WireTransaction(
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
val authenticatedArgs = commands.map {
val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) }
- AuthenticatedObject(it.signers, parties, it.value)
+ CommandWithParties(it.signers, parties, it.value)
}
// Open attachments specified in this transaction. If we haven't downloaded them, we fail.
val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
@@ -105,69 +106,6 @@ data class WireTransaction(
*/
val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(availableComponentHashes) }
- /**
- * Construction of partial transaction from WireTransaction based on filtering.
- * Note that list of nonces to be sent is updated on the fly, based on the index of the filtered tx component.
- * @param filtering filtering over the whole WireTransaction
- * @returns FilteredLeaves used in PartialMerkleTree calculation and verification.
- */
- fun filterWithFun(filtering: Predicate