mirror of
https://github.com/corda/corda.git
synced 2025-01-27 06:39:38 +00:00
Merge pull request #35 from corda/aslemmer-enterprise-merge-september-8
Aslemmer enterprise merge september 8
This commit is contained in:
commit
fdc73571e5
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -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
|
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -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.
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ local.properties
|
||||
**/build/*
|
||||
|
||||
lib/dokka.jar
|
||||
lib/quasar.jar
|
||||
|
||||
**/logs/*
|
||||
|
||||
|
21
.idea/compiler.xml
generated
21
.idea/compiler.xml
generated
@ -62,8 +62,9 @@
|
||||
<module name="node-api_test" target="1.8" />
|
||||
<module name="node-capsule_main" target="1.6" />
|
||||
<module name="node-capsule_test" target="1.6" />
|
||||
<module name="node-schemas_main" target="1.8" />
|
||||
<module name="node-schemas_test" target="1.8" />
|
||||
<module name="node-driver_integrationTest" target="1.8" />
|
||||
<module name="node-driver_main" target="1.8" />
|
||||
<module name="node-driver_test" target="1.8" />
|
||||
<module name="node_integrationTest" target="1.8" />
|
||||
<module name="node_main" target="1.8" />
|
||||
<module name="node_smokeTest" target="1.8" />
|
||||
@ -91,13 +92,15 @@
|
||||
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
||||
<module name="simm-valuation-demo_main" target="1.8" />
|
||||
<module name="simm-valuation-demo_test" target="1.8" />
|
||||
<module name="smoke-test-utils_main" target="1.8" />
|
||||
<module name="smoke-test-utils_test" target="1.8" />
|
||||
<module name="test-common_main" target="1.8" />
|
||||
<module name="test-common_test" target="1.8" />
|
||||
<module name="test-utils_integrationTest" target="1.8" />
|
||||
<module name="test-utils_main" target="1.8" />
|
||||
<module name="test-utils_test" target="1.8" />
|
||||
<module name="testing-node-driver_integrationTest" target="1.8" />
|
||||
<module name="testing-node-driver_main" target="1.8" />
|
||||
<module name="testing-node-driver_test" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_main" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_test" target="1.8" />
|
||||
<module name="testing-test-common_main" target="1.8" />
|
||||
<module name="testing-test-common_test" target="1.8" />
|
||||
<module name="testing-test-utils_main" target="1.8" />
|
||||
<module name="testing-test-utils_test" target="1.8" />
|
||||
<module name="tools_main" target="1.8" />
|
||||
<module name="tools_test" target="1.8" />
|
||||
<module name="trader-demo_integrationTest" target="1.8" />
|
||||
|
18
build.gradle
18
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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<in T : Any> @JvmOverloads constructor(
|
||||
Pair(name, argStr)
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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<Any?> = parser.parseArguments(clazz.name, names.zip(ctor.parameterTypes), "alternativeWord: Foo bar!")
|
||||
assertArrayEquals(args, arrayOf<Any?>("Foo bar!"))
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ dependencies {
|
||||
|
||||
// Integration test helpers
|
||||
integrationTestCompile "junit:junit:$junit_version"
|
||||
integrationTestCompile project(':node-driver')
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
|
@ -58,13 +58,13 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
||||
startFlowPermission<CashPaymentFlow>(),
|
||||
startFlowPermission<CashExitFlow>())
|
||||
)
|
||||
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()
|
||||
|
@ -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<Currency>, to: Currency) =
|
||||
Amount(exchangeDouble(amount, to).toLong(), to)
|
||||
|
||||
fun ExchangeRate.exchangeDouble(amount: Amount<Currency>, 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<Currency>, 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<ExchangeRate> = SimpleObjectProperty<ExchangeRate>(object : ExchangeRate {
|
||||
override fun rate(from: Currency, to: Currency) = 1.0
|
||||
val exchangeRate: ObservableValue<ExchangeRate> = SimpleObjectProperty<ExchangeRate>(object : ExchangeRate() {
|
||||
override fun rate(from: Currency, to: Currency) = BigDecimal.ONE
|
||||
})
|
||||
}
|
||||
|
@ -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<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from {
|
||||
publicKey ->
|
||||
publicKey?.let { rpcProxy.map { it?.nodeIdentityFromParty(AnonymousParty(publicKey)) } }
|
||||
})
|
||||
|
||||
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { !it.isCordaService() }
|
||||
val notaries: ObservableList<NodeInfo> = 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<NodeInfo?> = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey.keys.any { it == publicKey } }) {
|
||||
it.legalIdentity.owningKey.keys.any { it == publicKey }
|
||||
}
|
||||
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -29,7 +29,7 @@ object AmountBindings {
|
||||
return EasyBind.combine(observableCurrency, observableExchangeRate) { currency, exchangeRate ->
|
||||
Pair<Currency, (Amount<Currency>) -> Long>(
|
||||
currency,
|
||||
{ (quantity, _, token) -> (exchangeRate.rate(token, currency) * quantity).toLong() }
|
||||
{ amount -> exchangeRate.exchangeAmount(amount, currency).quantity }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
@file:JvmName("ObservableFold")
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.application.Platform
|
||||
|
@ -1,3 +1,4 @@
|
||||
@file:JvmName("ObservableUtilities")
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.application.Platform
|
||||
|
@ -81,15 +81,16 @@ open class ReadOnlyBackedObservableMapBase<K, A, B> : ObservableMap<K, A> {
|
||||
throw UnsupportedOperationException("remove() can't be called on ReadOnlyObservableMapBase")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun <A, K> ObservableMap<K, A>.createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change<K, A> {
|
||||
return object : MapChangeListener.Change<K, A>(this) {
|
||||
override fun getKey() = key
|
||||
override fun wasRemoved() = removedValue != null
|
||||
override fun wasAdded() = addedValue != null
|
||||
override fun getValueRemoved() = removedValue
|
||||
override fun getValueAdded() = addedValue
|
||||
/**
|
||||
* Construct an object modelling the given change to an observed map.
|
||||
*/
|
||||
fun createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change<K, A> {
|
||||
return object : MapChangeListener.Change<K, A>(this) {
|
||||
override fun getKey() = key
|
||||
override fun wasRemoved() = removedValue != null
|
||||
override fun wasAdded() = addedValue != null
|
||||
override fun getValueRemoved() = removedValue
|
||||
override fun getValueAdded() = addedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import java.util.*
|
||||
* [Generator.choice] picks a generator from the specified list and runs that.
|
||||
* [Generator.frequency] is similar to [choice] but the probability may be specified for each generator (it is normalised before picking).
|
||||
* [Generator.combine] combines two generators of A and B with a function (A, B) -> C. Variants exist for other arities.
|
||||
* [Generator.flatMap] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this
|
||||
* [Generator.flatMap] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this
|
||||
* function minimal as it may explode the stack, especially when using recursion.
|
||||
*
|
||||
* There are other utilities as well, the type of which are usually descriptive.
|
||||
@ -32,7 +32,6 @@ import java.util.*
|
||||
* The above will generate a random list of animals.
|
||||
*/
|
||||
class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
|
||||
|
||||
// Functor
|
||||
fun <B> map(function: (A) -> B): Generator<B> =
|
||||
Generator { generate(it).map(function) }
|
||||
@ -58,16 +57,42 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
|
||||
return Generator { random -> generate(random).flatMap { function(it).generate(random) } }
|
||||
}
|
||||
|
||||
fun generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
|
||||
var error: Throwable? = null
|
||||
for (i in 0..numberOfTries - 1) {
|
||||
val result = generate(random)
|
||||
error = when (result) {
|
||||
is Try.Success -> return result.value
|
||||
is Try.Failure -> result.exception
|
||||
}
|
||||
}
|
||||
if (error == null) {
|
||||
throw IllegalArgumentException("numberOfTries cannot be <= 0")
|
||||
} else {
|
||||
throw Exception("Failed to generate", error)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <A> pure(value: A) = Generator { Try.Success(value) }
|
||||
fun <A> impure(valueClosure: () -> A) = Generator { Try.Success(valueClosure()) }
|
||||
fun <A> fail(error: Exception) = Generator<A> { Try.Failure(error) }
|
||||
|
||||
// Alternative
|
||||
/**
|
||||
* Pick a generator from the specified list and run it.
|
||||
*/
|
||||
fun <A> choice(generators: List<Generator<A>>) = intRange(0, generators.size - 1).flatMap { generators[it] }
|
||||
|
||||
fun <A> success(generate: (SplittableRandom) -> A) = Generator { Try.Success(generate(it)) }
|
||||
/**
|
||||
* Pick a generator from the specified list, with a probability assigned to each generator, then run the
|
||||
* chosen generator.
|
||||
*
|
||||
* @param generators a list of probabilities of a generator being chosen, and generators. Probabilities must be
|
||||
* non-negative.
|
||||
*/
|
||||
fun <A> frequency(generators: List<Pair<Double, Generator<A>>>): Generator<A> {
|
||||
require(generators.all { it.first >= 0.0 }) { "Probabilities must not be negative" }
|
||||
val ranges = mutableListOf<Pair<Double, Double>>()
|
||||
var current = 0.0
|
||||
generators.forEach {
|
||||
@ -88,6 +113,8 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
|
||||
}
|
||||
}
|
||||
|
||||
fun <A> frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
|
||||
|
||||
fun <A> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
|
||||
val result = mutableListOf<A>()
|
||||
for (generator in generators) {
|
||||
@ -99,129 +126,113 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
|
||||
}
|
||||
Try.Success(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <A> Generator.Companion.frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
|
||||
|
||||
fun <A> Generator<A>.generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
|
||||
var error: Throwable? = null
|
||||
for (i in 0..numberOfTries - 1) {
|
||||
val result = generate(random)
|
||||
error = when (result) {
|
||||
is Try.Success -> return result.value
|
||||
is Try.Failure -> result.exception
|
||||
fun int() = Generator.success(SplittableRandom::nextInt)
|
||||
fun long() = Generator.success(SplittableRandom::nextLong)
|
||||
fun bytes(size: Int): Generator<ByteArray> = Generator.success { random ->
|
||||
ByteArray(size) { random.nextInt().toByte() }
|
||||
}
|
||||
}
|
||||
if (error == null) {
|
||||
throw IllegalArgumentException("numberOfTries cannot be <= 0")
|
||||
} else {
|
||||
throw Exception("Failed to generate", error)
|
||||
}
|
||||
}
|
||||
|
||||
fun Generator.Companion.int() = Generator.success(SplittableRandom::nextInt)
|
||||
fun Generator.Companion.long() = Generator.success(SplittableRandom::nextLong)
|
||||
fun Generator.Companion.bytes(size: Int): Generator<ByteArray> = Generator.success { random ->
|
||||
ByteArray(size) { random.nextInt().toByte() }
|
||||
}
|
||||
fun intRange(range: IntRange) = intRange(range.first, range.last)
|
||||
fun intRange(from: Int, to: Int): Generator<Int> = Generator.success {
|
||||
(from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
|
||||
}
|
||||
|
||||
fun Generator.Companion.intRange(range: IntRange) = intRange(range.first, range.last)
|
||||
fun Generator.Companion.intRange(from: Int, to: Int): Generator<Int> = Generator.success {
|
||||
(from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
|
||||
}
|
||||
fun longRange(range: LongRange) = longRange(range.first, range.last)
|
||||
fun longRange(from: Long, to: Long): Generator<Long> = Generator.success {
|
||||
(from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
|
||||
}
|
||||
|
||||
fun Generator.Companion.longRange(range: LongRange) = longRange(range.first, range.last)
|
||||
fun Generator.Companion.longRange(from: Long, to: Long): Generator<Long> = Generator.success {
|
||||
(from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
|
||||
}
|
||||
fun double() = Generator.success { it.nextDouble() }
|
||||
fun doubleRange(from: Double, to: Double): Generator<Double> = Generator.success {
|
||||
from + it.nextDouble() * (to - from)
|
||||
}
|
||||
|
||||
fun Generator.Companion.double() = Generator.success { it.nextDouble() }
|
||||
fun Generator.Companion.doubleRange(from: Double, to: Double): Generator<Double> = Generator.success {
|
||||
from + it.nextDouble() * (to - from)
|
||||
}
|
||||
|
||||
fun Generator.Companion.char() = Generator {
|
||||
val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16))
|
||||
if (Character.isValidCodePoint(codePoint)) {
|
||||
return@Generator Try.Success(codePoint.toChar())
|
||||
} else {
|
||||
Try.Failure(IllegalStateException("Could not generate valid codepoint"))
|
||||
}
|
||||
}
|
||||
|
||||
fun Generator.Companion.string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map {
|
||||
val builder = StringBuilder()
|
||||
it.forEach {
|
||||
builder.append(it)
|
||||
}
|
||||
builder.toString()
|
||||
}
|
||||
|
||||
fun <A> Generator.Companion.replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
|
||||
val generators = mutableListOf<Generator<A>>()
|
||||
for (i in 1..number) {
|
||||
generators.add(generator)
|
||||
}
|
||||
return sequence(generators)
|
||||
}
|
||||
|
||||
|
||||
fun <A> Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator<List<A>> {
|
||||
val chance = (meanSize - 1) / meanSize
|
||||
val result = mutableListOf<A>()
|
||||
var finish = false
|
||||
while (!finish) {
|
||||
val res = Generator.doubleRange(0.0, 1.0).generate(it).flatMap { value ->
|
||||
if (value < chance) {
|
||||
generator.generate(it).map { result.add(it) }
|
||||
fun char() = Generator {
|
||||
val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16))
|
||||
if (Character.isValidCodePoint(codePoint)) {
|
||||
return@Generator Try.Success(codePoint.toChar())
|
||||
} else {
|
||||
finish = true
|
||||
if (result.isEmpty() && atLeastOne) {
|
||||
generator.generate(it).map { result.add(it) }
|
||||
} else Try.Success(Unit)
|
||||
Try.Failure(IllegalStateException("Could not generate valid codepoint"))
|
||||
}
|
||||
}
|
||||
if (res is Try.Failure) {
|
||||
return@Generator res
|
||||
|
||||
fun string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map {
|
||||
val builder = StringBuilder()
|
||||
it.forEach {
|
||||
builder.append(it)
|
||||
}
|
||||
builder.toString()
|
||||
}
|
||||
}
|
||||
Try.Success(result)
|
||||
}
|
||||
|
||||
fun <A> Generator.Companion.pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
|
||||
fun <A> Generator.Companion.pickN(number: Int, list: List<A>) = Generator<List<A>> {
|
||||
val mask = BitSet(list.size)
|
||||
val size = Math.min(list.size, number)
|
||||
for (i in 0..size - 1) {
|
||||
// mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit
|
||||
mask[i] = true
|
||||
}
|
||||
for (i in 0..list.size - 1) {
|
||||
val bit = mask[i]
|
||||
val swapIndex = i + it.nextInt(size - i)
|
||||
mask[i] = mask[swapIndex]
|
||||
mask[swapIndex] = bit
|
||||
}
|
||||
val resultList = ArrayList<A>()
|
||||
list.forEachIndexed { index, a ->
|
||||
if (mask[index]) {
|
||||
resultList.add(a)
|
||||
fun <A> replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
|
||||
val generators = mutableListOf<Generator<A>>()
|
||||
for (i in 1..number) {
|
||||
generators.add(generator)
|
||||
}
|
||||
return sequence(generators)
|
||||
}
|
||||
}
|
||||
Try.Success(resultList)
|
||||
}
|
||||
|
||||
fun <A> Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
|
||||
sampleBernoulli(listOf(collection), maxRatio)
|
||||
|
||||
fun <A> Generator.Companion.sampleBernoulli(collection: Collection<A>, meanRatio: Double = 1.0): Generator<List<A>> =
|
||||
replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
|
||||
fun <A> replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator<List<A>> {
|
||||
val chance = (meanSize - 1) / meanSize
|
||||
val result = mutableListOf<A>()
|
||||
collection.forEachIndexed { index, element ->
|
||||
if (chances[index] < meanRatio) {
|
||||
result.add(element)
|
||||
var finish = false
|
||||
while (!finish) {
|
||||
val res = Generator.doubleRange(0.0, 1.0).generate(it).flatMap { value ->
|
||||
if (value < chance) {
|
||||
generator.generate(it).map { result.add(it) }
|
||||
} else {
|
||||
finish = true
|
||||
if (result.isEmpty() && atLeastOne) {
|
||||
generator.generate(it).map { result.add(it) }
|
||||
} else Try.Success(Unit)
|
||||
}
|
||||
}
|
||||
if (res is Try.Failure) {
|
||||
return@Generator res
|
||||
}
|
||||
}
|
||||
result
|
||||
Try.Success(result)
|
||||
}
|
||||
|
||||
fun <A> pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
|
||||
fun <A> pickN(number: Int, list: List<A>) = Generator<List<A>> {
|
||||
val mask = BitSet(list.size)
|
||||
val size = Math.min(list.size, number)
|
||||
for (i in 0..size - 1) {
|
||||
// mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit
|
||||
mask[i] = true
|
||||
}
|
||||
for (i in 0..list.size - 1) {
|
||||
val bit = mask[i]
|
||||
val swapIndex = i + it.nextInt(size - i)
|
||||
mask[i] = mask[swapIndex]
|
||||
mask[swapIndex] = bit
|
||||
}
|
||||
val resultList = ArrayList<A>()
|
||||
list.forEachIndexed { index, a ->
|
||||
if (mask[index]) {
|
||||
resultList.add(a)
|
||||
}
|
||||
}
|
||||
Try.Success(resultList)
|
||||
}
|
||||
|
||||
fun <A> sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
|
||||
sampleBernoulli(listOf(collection), maxRatio)
|
||||
|
||||
fun <A> sampleBernoulli(collection: Collection<A>, meanRatio: Double = 1.0): Generator<List<A>> {
|
||||
return replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
|
||||
val result = mutableListOf<A>()
|
||||
collection.forEachIndexed { index, element ->
|
||||
if (chances[index] < meanRatio) {
|
||||
result.add(element)
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
@file:JvmName("Generators")
|
||||
package net.corda.client.mock
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
|
@ -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!
|
||||
|
@ -82,6 +82,19 @@ class RPCClientProxyHandler(
|
||||
val log = loggerFor<RPCClientProxyHandler>()
|
||||
// To check whether toString() is being invoked
|
||||
val toStringMethod: Method = Object::toString.javaMethod!!
|
||||
|
||||
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) {
|
||||
var currentThrowable = throwable
|
||||
while (true) {
|
||||
val cause = currentThrowable.cause
|
||||
if (cause == null) {
|
||||
currentThrowable.initCause(callSite)
|
||||
break
|
||||
} else {
|
||||
currentThrowable = cause
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used for reaping
|
||||
@ -393,6 +406,19 @@ object RpcClientObservableSerializer : Serializer<Observable<*>>() {
|
||||
return serializationContext.withProperty(RpcObservableContextKey, observableContext)
|
||||
}
|
||||
|
||||
private fun <T> pinInSubscriptions(observable: Observable<T>, hardReferenceStore: MutableSet<Observable<*>>): Observable<T> {
|
||||
val refCount = AtomicInteger(0)
|
||||
return observable.doOnSubscribe {
|
||||
if (refCount.getAndIncrement() == 0) {
|
||||
require(hardReferenceStore.add(observable)) { "Reference store already contained reference $this on add" }
|
||||
}
|
||||
}.doOnUnsubscribe {
|
||||
if (refCount.decrementAndGet() == 0) {
|
||||
require(hardReferenceStore.remove(observable)) { "Reference store did not contain reference $this on remove" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<Observable<*>>): Observable<Any> {
|
||||
val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext
|
||||
val observableId = RPCApi.ObservableId(input.readLong(true))
|
||||
@ -405,7 +431,7 @@ object RpcClientObservableSerializer : Serializer<Observable<*>>() {
|
||||
observableContext.callSiteMap?.put(observableId.toLong, rpcCallSite)
|
||||
// We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users
|
||||
// don't need to store a reference to the Observables themselves.
|
||||
return observable.pinInSubscriptions(observableContext.hardReferenceStore).doOnUnsubscribe {
|
||||
return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe {
|
||||
// This causes Future completions to give warnings because the corresponding OnComplete sent from the server
|
||||
// will arrive after the client unsubscribes from the observable and consequently invalidates the mapping.
|
||||
// The unsubscribe is due to [ObservableToFuture]'s use of first().
|
||||
@ -421,30 +447,4 @@ object RpcClientObservableSerializer : Serializer<Observable<*>>() {
|
||||
val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as Long
|
||||
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) {
|
||||
var currentThrowable = throwable
|
||||
while (true) {
|
||||
val cause = currentThrowable.cause
|
||||
if (cause == null) {
|
||||
currentThrowable.initCause(callSite)
|
||||
break
|
||||
} else {
|
||||
currentThrowable = cause
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> Observable<T>.pinInSubscriptions(hardReferenceStore: MutableSet<Observable<*>>): Observable<T> {
|
||||
val refCount = AtomicInteger(0)
|
||||
return this.doOnSubscribe {
|
||||
if (refCount.getAndIncrement() == 0) {
|
||||
require(hardReferenceStore.add(this)) { "Reference store already contained reference $this on add" }
|
||||
}
|
||||
}.doOnUnsubscribe {
|
||||
if (refCount.decrementAndGet() == 0) {
|
||||
require(hardReferenceStore.remove(this)) { "Reference store did not contain reference $this on remove" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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<Unserializable>
|
||||
fun kotlinNPE()
|
||||
fun kotlinNPEAsync(): CordaFuture<Unit>
|
||||
}
|
||||
|
||||
class OpsImpl : Ops {
|
||||
override val protocolVersion = 1
|
||||
override fun getUnserializable() = Unserializable()
|
||||
override fun getUnserializableAsync(): CordaFuture<Unserializable> {
|
||||
return openFuture<Unserializable>().apply { capture { getUnserializable() } }
|
||||
}
|
||||
|
||||
override fun kotlinNPE() {
|
||||
(null as Any?)!!.hashCode()
|
||||
}
|
||||
|
||||
override fun kotlinNPEAsync(): CordaFuture<Unit> {
|
||||
return openFuture<Unit>().apply { capture { kotlinNPE() } }
|
||||
}
|
||||
}
|
||||
|
||||
private fun rpc(proc: (Ops) -> Any?): Unit = rpcDriver {
|
||||
val server = startRpcServer(ops = OpsImpl()).getOrThrow()
|
||||
proc(startRpcClient<Ops>(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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -1,5 +1,5 @@
|
||||
gradlePluginsVersion=0.15.1
|
||||
gradlePluginsVersion=0.16.2
|
||||
kotlinVersion=1.1.4
|
||||
guavaVersion=21.0
|
||||
bouncycastleVersion=1.57
|
||||
typesafeConfigVersion=1.3.1
|
||||
typesafeConfigVersion=1.3.1
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
@file:JvmName("ConcurrencyUtils")
|
||||
package net.corda.core.concurrent
|
||||
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
|
@ -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
|
||||
|
42
core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
Normal file
42
core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
Normal file
@ -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<Party>
|
||||
}
|
@ -32,33 +32,33 @@ inline fun <R> 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 <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signer: PublicKey? = null,
|
||||
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.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 <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signers: Collection<PublicKey>?,
|
||||
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signers: Collection<PublicKey>?,
|
||||
parties: Collection<Party>?) =
|
||||
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 <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand() = try {
|
||||
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand() = try {
|
||||
select<T>().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 <C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand(klass: Class<C>) =
|
||||
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject<C> else null }.single()
|
||||
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>) =
|
||||
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as CommandWithParties<C> 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 <C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingle
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
||||
commands: List<AuthenticatedObject<CommandData>>)
|
||||
commands: List<CommandWithParties<CommandData>>)
|
||||
: 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
|
||||
|
@ -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<PublicKey>): Boolean
|
||||
}
|
||||
// DOCEND 2
|
||||
|
||||
@ -289,7 +277,7 @@ abstract class TypeOnlyCommandData : CommandData {
|
||||
data class Command<T : CommandData>(val value: T, val signers: List<PublicKey>) {
|
||||
// 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<out UpgradedContract<*, *>>) : 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<out T : Any>(
|
||||
data class CommandWithParties<out T : CommandData>(
|
||||
val signers: List<PublicKey>,
|
||||
/** If any public keys were recognised, the looked up institutions are available here */
|
||||
val signingParties: List<Party>,
|
||||
@ -367,69 +355,6 @@ interface UpgradedContract<in OldState : ContractState, out NewState : ContractS
|
||||
fun upgrade(state: OldState): NewState
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) }
|
||||
}
|
||||
|
||||
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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -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<NodeAn
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes single PublicKey and checks if CompositeKey requirements hold for that key.
|
||||
* Takes single [PublicKey] and checks if [CompositeKey] requirements hold for that key.
|
||||
*/
|
||||
fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
|
||||
|
||||
@ -209,7 +205,7 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of all leaf keys of that CompositeKey.
|
||||
* Set of all leaf keys of that [CompositeKey].
|
||||
*/
|
||||
val leafKeys: Set<PublicKey>
|
||||
get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
|
||||
@ -253,9 +249,14 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
|
||||
* the total (aggregated) weight of the children, effectively generating an "N of N" requirement.
|
||||
* During process removes single keys wrapped in [CompositeKey] and enforces ordering on child nodes.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* @param 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.
|
||||
* @throws IllegalArgumentException if the threshold value is invalid.
|
||||
* @throws IllegalStateException if the composite key that would be generated from the current state of the builder
|
||||
* is invalid (for example it would contain no keys).
|
||||
*/
|
||||
fun build(threshold: Int? = null): PublicKey {
|
||||
require(threshold == null || threshold > 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<NodeAn
|
||||
// Returning the only child node which is [PublicKey] itself. We need to avoid single-key [CompositeKey] instances,
|
||||
// as there are scenarios where developers expected the underlying key and its composite versions to be equivalent.
|
||||
children.first().node
|
||||
} else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
|
||||
} else throw IllegalStateException("Trying to build CompositeKey without child nodes.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands all [CompositeKey]s present in PublicKey iterable to set of single [PublicKey]s.
|
||||
* If an element of the set is a single PublicKey it gives just that key, if it is a [CompositeKey] it returns all leaf
|
||||
* keys for that composite element.
|
||||
*/
|
||||
val Iterable<PublicKey>.expandedCompositeKeys: Set<PublicKey>
|
||||
get() = flatMap { it.keys }.toSet()
|
@ -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 <T : KeySpec> engineGetKeySpec(key: Key, keySpec: Class<T>): T {
|
||||
// Only support [X509EncodedKeySpec].
|
||||
// Only support X509EncodedKeySpec.
|
||||
throw InvalidKeySpecException("Not implemented yet $key $keySpec")
|
||||
}
|
||||
|
@ -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")
|
@ -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")
|
||||
}
|
@ -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<String, Provider> = 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())
|
||||
|
@ -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<PublicKey> get() {
|
||||
return if (this is CompositeKey) this.leafKeys
|
||||
else setOf(this)
|
||||
}
|
||||
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
|
||||
|
||||
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
|
||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean {
|
||||
return if (this is CompositeKey) this.isFulfilledBy(otherKeys)
|
||||
else this in otherKeys
|
||||
}
|
||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): 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<PublicKey>): 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<TransactionSignature>.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
|
||||
|
@ -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
|
||||
|
@ -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>): MerkleTree {
|
||||
if (lastNodesList.size == 1) {
|
||||
return lastNodesList[0] //Root reached.
|
||||
return lastNodesList[0] // Root reached.
|
||||
} else {
|
||||
val newLevelHashes: MutableList<MerkleTree> = ArrayList()
|
||||
val n = lastNodesList.size
|
||||
|
22
core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
Normal file
22
core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
Normal file
@ -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<PublicKey> {
|
||||
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))
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -11,4 +11,3 @@ import net.corda.core.serialization.CordaSerializable
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)
|
||||
|
||||
|
@ -29,4 +29,5 @@ data class SignatureScheme(
|
||||
val signatureName: String,
|
||||
val algSpec: AlgorithmParameterSpec?,
|
||||
val keySize: Int?,
|
||||
val desc: String)
|
||||
val desc: String
|
||||
)
|
||||
|
@ -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)
|
@ -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<PublicKey> {
|
||||
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))
|
@ -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<in T>(val otherSide: Party,
|
||||
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
|
||||
constructor(otherSide: Party) : this(otherSide, Acceptor.tracker())
|
||||
companion object {
|
||||
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
||||
object APPROVING : ProgressTracker.Step("State replacement approved")
|
||||
|
@ -3,7 +3,7 @@ package net.corda.core.flows
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
@ -262,7 +262,9 @@ abstract class SignTransactionFlow(val otherParty: Party,
|
||||
* @param stx a partially signed transaction received from your counter-party.
|
||||
* @throws FlowException if the proposed transaction fails the checks.
|
||||
*/
|
||||
@Suspendable abstract protected fun checkTransaction(stx: SignedTransaction)
|
||||
@Suspendable
|
||||
@Throws(FlowException::class)
|
||||
abstract protected fun checkTransaction(stx: SignedTransaction)
|
||||
|
||||
@Suspendable private fun checkMySignatureRequired(stx: SignedTransaction, signingKey: PublicKey) {
|
||||
require(signingKey in stx.tx.requiredSigningKeys) {
|
||||
|
@ -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<OldState : ContractState, out NewState : ContractState>(
|
||||
originalState: StateAndRef<OldState>,
|
||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(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<UpgradeCommand>().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<out UpgradedContract<*, *>>
|
||||
) : FlowLogic<Void?>() {
|
||||
@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<UpgradeCommand>) {
|
||||
val command = commandData.value
|
||||
val participantKeys: Set<PublicKey> = input.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
|
||||
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<OldState : ContractState, out NewState : ContractState>(
|
||||
originalState: StateAndRef<OldState>,
|
||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
||||
|
||||
companion object {
|
||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||
stateRef: StateAndRef<OldState>,
|
||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||
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 <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||
stateRef: StateAndRef<OldState>,
|
||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||
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<Class<out UpgradedContract<ContractState, *>>>(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<UpgradeCommand>().single())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun verify(input: ContractState, output: ContractState, commandData: Command<UpgradeCommand>) {
|
||||
val command = commandData.value
|
||||
val participantKeys: Set<PublicKey> = input.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
|
||||
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<Class<out UpgradedContract<ContractState, *>>>) {
|
||||
// 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<ContractState>(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<ContractState>(0).state.data,
|
||||
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File> */
|
||||
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<CodeSigner>? = 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<CodeSigner>()).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)
|
||||
}
|
@ -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
|
||||
|
@ -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<SignedTransaction>
|
||||
@Deprecated("This method is intended only for internal use and will be removed from the public API soon.")
|
||||
fun internalVerifiedTransactionsSnapshot(): List<SignedTransaction>
|
||||
|
||||
/**
|
||||
* 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<List<SignedTransaction>, SignedTransaction>
|
||||
fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, 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 <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
|
||||
fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
|
||||
|
||||
/**
|
||||
* 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 <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
|
||||
fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
|
||||
|
||||
/**
|
||||
* 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<out UpgradedContract<*, *>>)
|
||||
|
||||
/**
|
||||
* 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<Void?>
|
||||
fun waitUntilNetworkReady(): CordaFuture<Void?>
|
||||
|
||||
// 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 <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
|
||||
@ -322,25 +319,25 @@ inline fun <reified T : ContractState> 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 <T : Any, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: () -> R
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java)
|
||||
|
||||
inline fun <T : Any, A, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T , A, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A) -> R,
|
||||
arg0: A
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0)
|
||||
|
||||
inline fun <T : Any, A, B, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B) -> R,
|
||||
arg0: A,
|
||||
arg1: B
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1)
|
||||
|
||||
inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C) -> R,
|
||||
arg0: A,
|
||||
@ -348,7 +345,7 @@ inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
arg2: C
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2)
|
||||
|
||||
inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C, D) -> R,
|
||||
arg0: A,
|
||||
@ -357,7 +354,7 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow
|
||||
arg3: D
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||
|
||||
inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C, D, E) -> R,
|
||||
arg0: A,
|
||||
@ -367,7 +364,7 @@ inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startF
|
||||
arg4: E
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
|
||||
|
||||
inline fun <T : Any, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C, D, E, F) -> R,
|
||||
arg0: A,
|
||||
@ -382,20 +379,20 @@ inline fun <T : Any, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.sta
|
||||
* Same again, except this time with progress-tracking enabled.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: () -> R
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A) -> R,
|
||||
arg0: A
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B) -> R,
|
||||
arg0: A,
|
||||
@ -403,7 +400,7 @@ inline fun <T : Any, A, B, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlo
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C) -> R,
|
||||
arg0: A,
|
||||
@ -412,7 +409,7 @@ inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startTracked
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C, D) -> R,
|
||||
arg0: A,
|
||||
@ -422,7 +419,7 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrac
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C, D, E) -> R,
|
||||
arg0: A,
|
||||
@ -433,7 +430,7 @@ inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startT
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C, D, E, F) -> R,
|
||||
arg0: A,
|
||||
|
@ -57,16 +57,11 @@ data class FlowProgressHandleImpl<A>(
|
||||
|
||||
// Remember to add @Throws to FlowProgressHandle.close() if this throws an exception.
|
||||
override fun close() {
|
||||
progress.notUsed()
|
||||
try {
|
||||
progress.subscribe({}, {}).unsubscribe()
|
||||
} catch (e: Exception) {
|
||||
// Swallow any other exceptions as well.
|
||||
}
|
||||
returnValue.cancel(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Private copy of the version in client:rpc.
|
||||
private fun <T> Observable<T>.notUsed() {
|
||||
try {
|
||||
this.subscribe({}, {}).unsubscribe()
|
||||
} catch (e: Exception) {
|
||||
// Swallow any other exceptions as well.
|
||||
}
|
||||
}
|
||||
}
|
@ -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<NetworkHostAndPort>,
|
||||
val legalIdentityAndCert: PartyAndCertificate, //TODO This field will be removed in future PR which gets rid of services.
|
||||
val legalIdentitiesAndCerts: NonEmptySet<PartyAndCertificate>,
|
||||
// TODO After removing of services these two fields will be merged together and made NonEmptySet.
|
||||
val legalIdentityAndCert: PartyAndCertificate,
|
||||
val legalIdentitiesAndCerts: Set<PartyAndCertificate>,
|
||||
val platformVersion: Int,
|
||||
val advertisedServices: List<ServiceEntry> = 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<NetworkHostAndPort>,
|
||||
fun serviceIdentities(type: ServiceType): List<Party> {
|
||||
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] }
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<out UpgradedContract<*, *>>)
|
||||
|
||||
/** Remove a previously authorised state ref */
|
||||
fun removeAuthorisedContractUpgrade(ref: StateRef)
|
||||
}
|
@ -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<MapChange>
|
||||
/** Future to track completion of the NetworkMapService registration. */
|
||||
val mapServiceRegistered: CordaFuture<Void?>
|
||||
val nodeReady: CordaFuture<Void?>
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
@ -25,5 +25,3 @@ data class ServiceInfo(val type: ServiceType, val name: X500Name? = null) {
|
||||
|
||||
override fun toString() = if (name != null) "$type|$name" else type.toString()
|
||||
}
|
||||
|
||||
fun Iterable<ServiceInfo>.containsType(type: ServiceType) = any { it.type == type }
|
||||
|
@ -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)
|
||||
|
@ -173,17 +173,6 @@ interface VaultService {
|
||||
*/
|
||||
val updatesPublisher: PublishSubject<Vault.Update<ContractState>>
|
||||
|
||||
/**
|
||||
* 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<CoreTransaction>)
|
||||
|
||||
/** 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<out UpgradedContract<*, *>>?
|
||||
|
||||
/**
|
||||
* 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<out UpgradedContract<*, *>>)
|
||||
|
||||
/**
|
||||
* 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].
|
||||
|
@ -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.*
|
||||
|
125
core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
Normal file
125
core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
Normal file
@ -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<NodeInfoSchemaV1.DBHostAndPort>,
|
||||
|
||||
@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<DBPartyAndCertificate>,
|
||||
|
||||
@Column(name = "platform_version")
|
||||
val platformVersion: Int,
|
||||
|
||||
@Column(name = "advertised_services")
|
||||
@ElementCollection
|
||||
var advertisedServices: List<DBServiceEntry> = 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<ServiceEntry>() ?: 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<PersistentNodeInfo> = 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<PartyAndCertificate>()
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T
|
||||
abstract fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, 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 <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T>
|
||||
abstract fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T>
|
||||
|
||||
/**
|
||||
* 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<SerializationContext?>()
|
||||
|
||||
/**
|
||||
* Change the current context inside the block to that supplied.
|
||||
*/
|
||||
fun <T> 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 <T> 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<SerializationFactory?>()
|
||||
|
||||
/**
|
||||
* 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,13 @@ 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).
|
||||
*/
|
||||
@Throws(MissingAttachmentsException::class)
|
||||
fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): SerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the given class specifically whitelisted.
|
||||
*/
|
||||
@ -107,26 +168,26 @@ object SerializationDefaults {
|
||||
/**
|
||||
* Convenience extension method for deserializing a ByteSequence, utilising the defaults.
|
||||
*/
|
||||
inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T {
|
||||
inline fun <reified T : Any> 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 <reified T : Any> SerializedBytes<T>.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T {
|
||||
inline fun <reified T : Any> SerializedBytes<T>.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 <reified T : Any> ByteArray.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T = this.sequence().deserialize(serializationFactory, context)
|
||||
inline fun <reified T : Any> 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 : Any> T.serialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): SerializedBytes<T> {
|
||||
fun <T : Any> T.serialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): SerializedBytes<T> {
|
||||
return serializationFactory.serialize(this, context)
|
||||
}
|
||||
|
||||
@ -142,4 +203,4 @@ class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
|
||||
interface ClassWhitelist {
|
||||
fun hasListed(type: Class<*>): Boolean
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
interface SerializationCustomization {
|
||||
fun addToWhitelist(type: Class<*>)
|
||||
fun addToWhitelist(vararg types: Class<*>)
|
||||
}
|
||||
|
||||
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<StateAndRef<ContractState>>,
|
||||
override val outputs: List<TransactionState<ContractState>>,
|
||||
/** Arbitrary data passed to the program of each input state. */
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
val commands: List<CommandWithParties<CommandData>>,
|
||||
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||
val attachments: List<Attachment>,
|
||||
/** The hash of the original serialised WireTransaction. */
|
||||
|
@ -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 <T : Any> serializedHash(x: T, privacySalt: PrivacySalt?, index: Int): Secur
|
||||
|
||||
fun <T : Any> 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 <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = P2P_CONTEXT.withoutReferences()).bytes.sha256()
|
||||
fun <T : Any> 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<Any>
|
||||
): 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<Any>): FilteredLeaves {
|
||||
val nonces: MutableList<SecureHash> = 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 <T : Any> 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<Int> {
|
||||
// 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<SecureHash> = filteredLeaves.availableComponentHashes
|
||||
if (hashes.isEmpty())
|
||||
throw MerkleTreeException("Transaction without included leaves.")
|
||||
return partialMerkleTree.verify(rootHash, hashes)
|
||||
return partialMerkleTree.verify(id, hashes)
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,10 @@ 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 net.corda.core.serialization.CordaSerializable
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
@ -13,6 +14,7 @@ import java.security.PublicKey
|
||||
* old and new notaries. Output states can be computed by applying the notary modification to corresponding inputs
|
||||
* on the fly.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NotaryChangeWireTransaction(
|
||||
override val inputs: List<StateRef>,
|
||||
override val notary: Party,
|
||||
|
@ -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<CoreTransaction>,
|
||||
override val sigs: List<TransactionSignature>
|
||||
) : TransactionWithSignatures {
|
||||
@ -50,12 +52,18 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
/** 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<Any>) = tx.buildFilteredTransaction(filtering)
|
||||
|
||||
/** Helper to access the inputs of the contained transaction */
|
||||
val inputs: List<StateRef> get() = transaction.inputs
|
||||
/** Helper to access the notary of the contained transaction */
|
||||
|
@ -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<Any>): FilteredLeaves {
|
||||
val nonces: MutableList<SecureHash> = mutableListOf()
|
||||
val offsets = indexOffsets()
|
||||
fun notNullFalseAndNoncesUpdate(elem: Any?, index: Int): Any? {
|
||||
return if (elem == null || !filtering.test(elem)) {
|
||||
null
|
||||
} else {
|
||||
nonces.add(computeNonce(privacySalt, index))
|
||||
elem
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> filterAndNoncesUpdate(t: T, index: Int): Boolean {
|
||||
return if (filtering.test(t)) {
|
||||
nonces.add(computeNonce(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(
|
||||
inputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index) },
|
||||
attachments.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[0]) },
|
||||
outputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[1]) },
|
||||
commands.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[2]) },
|
||||
notNullFalseAndNoncesUpdate(notary, offsets[3]) as Party?,
|
||||
notNullFalseAndNoncesUpdate(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(): List<Int> {
|
||||
// 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(inputs.size, inputs.size + attachments.size)
|
||||
offsets.add(offsets.last() + outputs.size)
|
||||
offsets.add(offsets.last() + commands.size)
|
||||
if (notary != null) {
|
||||
offsets.add(offsets.last() + 1)
|
||||
} else {
|
||||
offsets.add(offsets.last())
|
||||
}
|
||||
if (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
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
|
||||
*
|
||||
|
253
core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt
Normal file
253
core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt
Normal file
@ -0,0 +1,253 @@
|
||||
package net.corda.core.utilities
|
||||
|
||||
val countryCodes = hashSetOf(
|
||||
"AF",
|
||||
"AX",
|
||||
"AL",
|
||||
"DZ",
|
||||
"AS",
|
||||
"AD",
|
||||
"AO",
|
||||
"AI",
|
||||
"AQ",
|
||||
"AG",
|
||||
"AR",
|
||||
"AM",
|
||||
"AW",
|
||||
"AU",
|
||||
"AT",
|
||||
"AZ",
|
||||
"BS",
|
||||
"BH",
|
||||
"BD",
|
||||
"BB",
|
||||
"BY",
|
||||
"BE",
|
||||
"BZ",
|
||||
"BJ",
|
||||
"BM",
|
||||
"BT",
|
||||
"BO",
|
||||
"BQ",
|
||||
"BA",
|
||||
"BW",
|
||||
"BV",
|
||||
"BR",
|
||||
"IO",
|
||||
"BN",
|
||||
"BG",
|
||||
"BF",
|
||||
"BI",
|
||||
"KH",
|
||||
"CM",
|
||||
"CA",
|
||||
"CV",
|
||||
"KY",
|
||||
"CF",
|
||||
"TD",
|
||||
"CL",
|
||||
"CN",
|
||||
"CX",
|
||||
"CC",
|
||||
"CO",
|
||||
"KM",
|
||||
"CG",
|
||||
"CD",
|
||||
"CK",
|
||||
"CR",
|
||||
"CI",
|
||||
"HR",
|
||||
"CU",
|
||||
"CW",
|
||||
"CY",
|
||||
"CZ",
|
||||
"DK",
|
||||
"DJ",
|
||||
"DM",
|
||||
"DO",
|
||||
"EC",
|
||||
"EG",
|
||||
"SV",
|
||||
"GQ",
|
||||
"ER",
|
||||
"EE",
|
||||
"ET",
|
||||
"FK",
|
||||
"FO",
|
||||
"FJ",
|
||||
"FI",
|
||||
"FR",
|
||||
"GF",
|
||||
"PF",
|
||||
"TF",
|
||||
"GA",
|
||||
"GM",
|
||||
"GE",
|
||||
"DE",
|
||||
"GH",
|
||||
"GI",
|
||||
"GR",
|
||||
"GL",
|
||||
"GD",
|
||||
"GP",
|
||||
"GU",
|
||||
"GT",
|
||||
"GG",
|
||||
"GN",
|
||||
"GW",
|
||||
"GY",
|
||||
"HT",
|
||||
"HM",
|
||||
"VA",
|
||||
"HN",
|
||||
"HK",
|
||||
"HU",
|
||||
"IS",
|
||||
"IN",
|
||||
"ID",
|
||||
"IR",
|
||||
"IQ",
|
||||
"IE",
|
||||
"IM",
|
||||
"IL",
|
||||
"IT",
|
||||
"JM",
|
||||
"JP",
|
||||
"JE",
|
||||
"JO",
|
||||
"KZ",
|
||||
"KE",
|
||||
"KI",
|
||||
"KP",
|
||||
"KR",
|
||||
"KW",
|
||||
"KG",
|
||||
"LA",
|
||||
"LV",
|
||||
"LB",
|
||||
"LS",
|
||||
"LR",
|
||||
"LY",
|
||||
"LI",
|
||||
"LT",
|
||||
"LU",
|
||||
"MO",
|
||||
"MK",
|
||||
"MG",
|
||||
"MW",
|
||||
"MY",
|
||||
"MV",
|
||||
"ML",
|
||||
"MT",
|
||||
"MH",
|
||||
"MQ",
|
||||
"MR",
|
||||
"MU",
|
||||
"YT",
|
||||
"MX",
|
||||
"FM",
|
||||
"MD",
|
||||
"MC",
|
||||
"MN",
|
||||
"ME",
|
||||
"MS",
|
||||
"MA",
|
||||
"MZ",
|
||||
"MM",
|
||||
"NA",
|
||||
"NR",
|
||||
"NP",
|
||||
"NL",
|
||||
"NC",
|
||||
"NZ",
|
||||
"NI",
|
||||
"NE",
|
||||
"NG",
|
||||
"NU",
|
||||
"NF",
|
||||
"MP",
|
||||
"NO",
|
||||
"OM",
|
||||
"PK",
|
||||
"PW",
|
||||
"PS",
|
||||
"PA",
|
||||
"PG",
|
||||
"PY",
|
||||
"PE",
|
||||
"PH",
|
||||
"PN",
|
||||
"PL",
|
||||
"PT",
|
||||
"PR",
|
||||
"QA",
|
||||
"RE",
|
||||
"RO",
|
||||
"RU",
|
||||
"RW",
|
||||
"BL",
|
||||
"SH",
|
||||
"KN",
|
||||
"LC",
|
||||
"MF",
|
||||
"PM",
|
||||
"VC",
|
||||
"WS",
|
||||
"SM",
|
||||
"ST",
|
||||
"SA",
|
||||
"SN",
|
||||
"RS",
|
||||
"SC",
|
||||
"SL",
|
||||
"SG",
|
||||
"SX",
|
||||
"SK",
|
||||
"SI",
|
||||
"SB",
|
||||
"SO",
|
||||
"ZA",
|
||||
"GS",
|
||||
"SS",
|
||||
"ES",
|
||||
"LK",
|
||||
"SD",
|
||||
"SR",
|
||||
"SJ",
|
||||
"SZ",
|
||||
"SE",
|
||||
"CH",
|
||||
"SY",
|
||||
"TW",
|
||||
"TJ",
|
||||
"TZ",
|
||||
"TH",
|
||||
"TL",
|
||||
"TG",
|
||||
"TK",
|
||||
"TO",
|
||||
"TT",
|
||||
"TN",
|
||||
"TR",
|
||||
"TM",
|
||||
"TC",
|
||||
"TV",
|
||||
"UG",
|
||||
"UA",
|
||||
"AE",
|
||||
"GB",
|
||||
"US",
|
||||
"UM",
|
||||
"UY",
|
||||
"UZ",
|
||||
"VU",
|
||||
"VE",
|
||||
"VN",
|
||||
"VG",
|
||||
"VI",
|
||||
"WF",
|
||||
"EH",
|
||||
"YE",
|
||||
"ZM",
|
||||
"ZW"
|
||||
)
|
@ -1,7 +1,9 @@
|
||||
@file:JvmName("EncodingUtils")
|
||||
|
||||
package net.corda.core.crypto
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.crypto.Base58
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import java.nio.charset.Charset
|
@ -2,8 +2,8 @@
|
||||
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.crypto.commonName
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import java.lang.Character.UnicodeScript.*
|
||||
import java.text.Normalizer
|
||||
import java.util.regex.Pattern
|
||||
@ -22,16 +22,9 @@ import javax.security.auth.x500.X500Principal
|
||||
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
||||
*/
|
||||
fun validateLegalName(normalizedLegalName: String) {
|
||||
legalNameRules.forEach { it.validate(normalizedLegalName) }
|
||||
Rule.legalNameRules.forEach { it.validate(normalizedLegalName) }
|
||||
}
|
||||
|
||||
// TODO: Implement X500 attribute validation once the specification has been finalised.
|
||||
fun validateX500Name(x500Name: X500Name) {
|
||||
validateLegalName(x500Name.commonName)
|
||||
}
|
||||
|
||||
val WHITESPACE = "\\s++".toRegex()
|
||||
|
||||
/**
|
||||
* The normalize function will trim the input string, replace any multiple spaces with a single space,
|
||||
* and normalize the string according to NFKC normalization form.
|
||||
@ -41,82 +34,137 @@ fun normaliseLegalName(legalName: String): String {
|
||||
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
|
||||
}
|
||||
|
||||
private val legalNameRules: List<Rule<String>> = listOf(
|
||||
UnicodeNormalizationRule(),
|
||||
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
||||
WordRule("node", "server"),
|
||||
LengthRule(maxLength = 255),
|
||||
// TODO: Implement confusable character detection if we add more scripts.
|
||||
UnicodeRangeRule(LATIN, COMMON, INHERITED),
|
||||
CapitalLetterRule(),
|
||||
X500NameRule(),
|
||||
MustHaveAtLeastTwoLettersRule()
|
||||
)
|
||||
val WHITESPACE = "\\s++".toRegex()
|
||||
|
||||
private class UnicodeNormalizationRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." }
|
||||
private val mandatoryAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L)
|
||||
private val supportedAttributes = mandatoryAttributes + setOf(BCStyle.CN, BCStyle.ST, BCStyle.OU)
|
||||
|
||||
/**
|
||||
* Validate X500Name according to Corda X500Name specification
|
||||
*
|
||||
* Supported attributes:
|
||||
* - organisation (O) – VARCHAR(127)
|
||||
* - state (ST) – VARCHAR(64) nullable
|
||||
* - locality (L) – VARCHAR(64)
|
||||
* - country (C) – VARCHAR(2) - ISO code of the country in which it is registered
|
||||
* - organizational-unit (OU) – VARCHAR(64) nullable
|
||||
* - common name (CN) – VARCHAR(64)
|
||||
*
|
||||
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
||||
* @see <a href="https://r3-cev.atlassian.net/wiki/spaces/AWG/pages/129206341/Distinguished+name+structure">Design Doc</a>.
|
||||
*/
|
||||
fun validateX500Name(x500Name: X500Name) {
|
||||
val rDNs = x500Name.rdNs.flatMap { it.typesAndValues.toList() }
|
||||
val attributes = rDNs.map { it.type }
|
||||
|
||||
// Duplicate attribute value checks.
|
||||
require(attributes.size == attributes.toSet().size) { "X500Name contain duplicate attribute." }
|
||||
|
||||
// Mandatory attribute checks.
|
||||
require(attributes.containsAll(mandatoryAttributes)) {
|
||||
val missingAttributes = mandatoryAttributes.subtract(attributes).map { BCStyle.INSTANCE.oidToDisplayName(it) }
|
||||
"The following attribute${if (missingAttributes.size > 1) "s are" else " is"} missing from the legal name : $missingAttributes"
|
||||
}
|
||||
|
||||
// Supported attribute checks.
|
||||
require(attributes.subtract(supportedAttributes).isEmpty()) {
|
||||
val unsupportedAttributes = attributes.subtract(supportedAttributes).map { BCStyle.INSTANCE.oidToDisplayName(it) }
|
||||
"The following attribute${if (unsupportedAttributes.size > 1) "s are" else " is"} not supported in Corda :$unsupportedAttributes"
|
||||
}
|
||||
// Legal name checks.
|
||||
validateLegalName(x500Name.organisation)
|
||||
|
||||
// Attribute data width checks.
|
||||
require(x500Name.country.length == 2) { "Invalid country '${x500Name.country}' Country code must be 2 letters ISO code " }
|
||||
require(x500Name.country.toUpperCase() == x500Name.country) { "Country code should be in upper case." }
|
||||
require(countryCodes.contains(x500Name.country)) { "Invalid country code '${x500Name.country}'" }
|
||||
|
||||
require(x500Name.organisation.length < 127) { "Organisation attribute (O) must contain less then 127 characters." }
|
||||
require(x500Name.locality.length < 64) { "Locality attribute (L) must contain less then 64 characters." }
|
||||
|
||||
x500Name.state?.let { require(it.length < 64) { "State attribute (ST) must contain less then 64 characters." } }
|
||||
x500Name.organisationUnit?.let { require(x500Name.organisationUnit!!.length < 64) { "Organisation Unit attribute (OU) must contain less then 64 characters." } }
|
||||
x500Name.commonName?.let { require(x500Name.commonName!!.length < 64) { "Common Name attribute (CN) must contain less then 64 characters." } }
|
||||
}
|
||||
|
||||
private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeScript) : Rule<String> {
|
||||
private val pattern = supportScripts.map { "\\p{Is$it}" }.joinToString(separator = "", prefix = "[", postfix = "]*").let { Pattern.compile(it) }
|
||||
private sealed class Rule<in T> {
|
||||
companion object {
|
||||
val legalNameRules: List<Rule<String>> = listOf(
|
||||
UnicodeNormalizationRule(),
|
||||
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
||||
WordRule("node", "server"),
|
||||
LengthRule(maxLength = 255),
|
||||
// TODO: Implement confusable character detection if we add more scripts.
|
||||
UnicodeRangeRule(LATIN, COMMON, INHERITED),
|
||||
CapitalLetterRule(),
|
||||
X500NameRule(),
|
||||
MustHaveAtLeastTwoLettersRule()
|
||||
)
|
||||
}
|
||||
|
||||
override fun validate(legalName: String) {
|
||||
require(pattern.matcher(legalName).matches()) {
|
||||
val illegalChars = legalName.replace(pattern.toRegex(), "").toSet()
|
||||
if (illegalChars.size > 1) {
|
||||
"Forbidden characters $illegalChars in \"$legalName\"."
|
||||
} else {
|
||||
"Forbidden character $illegalChars in \"$legalName\"."
|
||||
abstract fun validate(legalName: T)
|
||||
|
||||
private class UnicodeNormalizationRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." }
|
||||
}
|
||||
}
|
||||
|
||||
private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeScript) : Rule<String>() {
|
||||
private val pattern = supportScripts.map { "\\p{Is$it}" }.joinToString(separator = "", prefix = "[", postfix = "]*").let { Pattern.compile(it) }
|
||||
|
||||
override fun validate(legalName: String) {
|
||||
require(pattern.matcher(legalName).matches()) {
|
||||
val illegalChars = legalName.replace(pattern.toRegex(), "").toSet()
|
||||
if (illegalChars.size > 1) {
|
||||
"Forbidden characters $illegalChars in \"$legalName\"."
|
||||
} else {
|
||||
"Forbidden character $illegalChars in \"$legalName\"."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CharacterRule(vararg val bannedChars: Char) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
bannedChars.forEach {
|
||||
require(!legalName.contains(it, true)) { "Character not allowed in legal names: $it" }
|
||||
private class CharacterRule(vararg val bannedChars: Char) : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
bannedChars.forEach {
|
||||
require(!legalName.contains(it, true)) { "Character not allowed in legal names: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WordRule(vararg val bannedWords: String) : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
bannedWords.forEach {
|
||||
require(!legalName.contains(it, ignoreCase = true)) { "Word not allowed in legal names: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LengthRule(val maxLength: Int) : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
|
||||
}
|
||||
}
|
||||
|
||||
private class CapitalLetterRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
val capitalizedLegalName = legalName.capitalize()
|
||||
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
|
||||
}
|
||||
}
|
||||
|
||||
private class X500NameRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
|
||||
X500Principal("CN=$legalName")
|
||||
}
|
||||
}
|
||||
|
||||
private class MustHaveAtLeastTwoLettersRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
// Try to exclude names like "/", "£", "X" etc.
|
||||
require(legalName.count { it.isLetter() } >= 2) { "Illegal input legal name '$legalName'. Legal name must have at least two letters" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WordRule(vararg val bannedWords: String) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
bannedWords.forEach {
|
||||
require(!legalName.contains(it, ignoreCase = true)) { "Word not allowed in legal names: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LengthRule(val maxLength: Int) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
|
||||
}
|
||||
}
|
||||
|
||||
private class CapitalLetterRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
val capitalizedLegalName = legalName.capitalize()
|
||||
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
|
||||
}
|
||||
}
|
||||
|
||||
private class X500NameRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
|
||||
X500Principal("CN=$legalName")
|
||||
}
|
||||
}
|
||||
|
||||
private class MustHaveAtLeastTwoLettersRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
// Try to exclude names like "/", "£", "X" etc.
|
||||
require(legalName.count { it.isLetter() } >= 2) { "Illegal input legal name '$legalName'. Legal name must have at least two letters" }
|
||||
}
|
||||
}
|
||||
|
||||
private interface Rule<in T> {
|
||||
fun validate(legalName: T)
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
@file:JvmName("X500NameUtils")
|
||||
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
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
|
||||
|
||||
val X500Name.commonName: String? get() = getRDNValueString(BCStyle.CN)
|
||||
val X500Name.organisationUnit: String? get() = getRDNValueString(BCStyle.OU)
|
||||
val X500Name.state: String? get() = getRDNValueString(BCStyle.ST)
|
||||
val X500Name.organisation: String get() = getRDNValueString(BCStyle.O) ?: throw IllegalArgumentException("Malformed X500 name, organisation attribute (O) cannot be empty.")
|
||||
val X500Name.locality: String get() = getRDNValueString(BCStyle.L) ?: throw IllegalArgumentException("Malformed X500 name, locality attribute (L) cannot be empty.")
|
||||
val X500Name.country: String get() = getRDNValueString(BCStyle.C) ?: throw IllegalArgumentException("Malformed X500 name, country attribute (C) cannot be empty.")
|
||||
|
||||
private fun X500Name.getRDNValueString(identifier: ASN1ObjectIdentifier): String? = getRDNs(identifier).firstOrNull()?.first?.value?.toString()
|
||||
|
||||
val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
|
||||
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
|
||||
|
||||
/**
|
||||
* Generate a distinguished name from the provided X500 .
|
||||
*
|
||||
* @param O organisation name.
|
||||
* @param L locality.
|
||||
* @param C county.
|
||||
* @param CN common name.
|
||||
* @param OU organisation unit.
|
||||
* @param ST state.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun getX500Name(O: String, L: String, C: String, CN: String? = null, OU: String? = null, ST: String? = null): X500Name {
|
||||
return X500NameBuilder(BCStyle.INSTANCE).apply {
|
||||
addRDN(BCStyle.C, C)
|
||||
ST?.let { addRDN(BCStyle.ST, it) }
|
||||
addRDN(BCStyle.L, L)
|
||||
addRDN(BCStyle.O, O)
|
||||
OU?.let { addRDN(BCStyle.OU, it) }
|
||||
CN?.let { addRDN(BCStyle.CN, it) }
|
||||
}.build()
|
||||
}
|
||||
|
||||
fun X500Name.withCommonName(commonName: String?): X500Name {
|
||||
return getX500Name(organisation, locality, country, commonName, organisationUnit, state)
|
||||
}
|
||||
|
||||
fun X500Name.toWellFormattedName(): X500Name {
|
||||
validateX500Name(this)
|
||||
return getX500Name(organisation, locality, country, commonName, organisationUnit, state)
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
@ -1,15 +1,18 @@
|
||||
package net.corda.core.flows;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.testing.node.MockNetwork;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class FlowsInJavaTest {
|
||||
|
||||
@ -18,11 +21,13 @@ public class FlowsInJavaTest {
|
||||
private MockNetwork.MockNode node2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
public void setUp() throws Exception {
|
||||
MockNetwork.BasketOfNodes someNodes = mockNet.createSomeNodes(2);
|
||||
node1 = someNodes.getPartyNodes().get(0);
|
||||
node2 = someNodes.getPartyNodes().get(1);
|
||||
mockNet.runNetwork();
|
||||
// Ensure registration was successful
|
||||
node1.getNodeReadyFuture().get();
|
||||
}
|
||||
|
||||
@After
|
||||
@ -38,6 +43,30 @@ public class FlowsInJavaTest {
|
||||
assertThat(result.get()).isEqualTo("Hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void primitiveClassForReceiveType() throws InterruptedException {
|
||||
// Using the primitive classes causes problems with the checkpointing so we use the wrapper classes and convert
|
||||
// to the primitive class at callsite.
|
||||
for (Class<?> receiveType : Primitives.allWrapperTypes()) {
|
||||
primitiveReceiveTypeTest(receiveType);
|
||||
}
|
||||
}
|
||||
|
||||
private void primitiveReceiveTypeTest(Class<?> receiveType) throws InterruptedException {
|
||||
PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(node2.getInfo().getLegalIdentity(), receiveType);
|
||||
Future<?> result = node1.getServices().startFlow(flow).getResultFuture();
|
||||
mockNet.runNetwork();
|
||||
try {
|
||||
result.get();
|
||||
fail("ExecutionException should have been thrown");
|
||||
} catch (ExecutionException e) {
|
||||
assertThat(e.getCause())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("primitive")
|
||||
.hasMessageContaining(receiveType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
private static class SendInUnwrapFlow extends FlowLogic<String> {
|
||||
private final Party otherParty;
|
||||
@ -71,4 +100,21 @@ public class FlowsInJavaTest {
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
private static class PrimitiveReceiveFlow extends FlowLogic<Void> {
|
||||
private final Party otherParty;
|
||||
private final Class<?> receiveType;
|
||||
|
||||
private PrimitiveReceiveFlow(Party otherParty, Class<?> receiveType) {
|
||||
this.otherParty = otherParty;
|
||||
this.receiveType = receiveType;
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public Void call() throws FlowException {
|
||||
receive(Primitives.unwrap(receiveType), otherParty);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class AttachmentTest {
|
||||
val attachment = object : Attachment {
|
||||
override val id get() = throw UnsupportedOperationException()
|
||||
override fun open() = inputStream
|
||||
override val signers get() = throw UnsupportedOperationException()
|
||||
}
|
||||
try {
|
||||
attachment.openAsJAR()
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -96,7 +96,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
||||
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_NOTARY)
|
||||
val inputs = emptyList<StateAndRef<*>>()
|
||||
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
val commands = emptyList<CommandWithParties<CommandData>>()
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val timeWindow: TimeWindow? = null
|
||||
@ -137,7 +137,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
||||
val outState = inState.copy(notary = ALICE)
|
||||
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
|
||||
val outputs = listOf(outState)
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
val commands = emptyList<CommandWithParties<CommandData>>()
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val timeWindow: TimeWindow? = null
|
||||
|
@ -1,16 +1,17 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.crypto.composite.CompositeSignature
|
||||
import net.corda.core.crypto.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
|
||||
import net.corda.core.internal.declaredField
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.cert
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import net.corda.testing.kryoSpecific
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -88,7 +89,7 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
||||
val aliceAndBobOrCharlie = CompositeKey.Builder().addKeys(aliceAndBob, charliePublicKey).build(threshold = 1)
|
||||
|
||||
val encoded = aliceAndBobOrCharlie.toBase58String()
|
||||
val decoded = parsePublicKeyBase58(encoded)
|
||||
val decoded = net.corda.core.utilities.parsePublicKeyBase58(encoded)
|
||||
|
||||
assertEquals(decoded, aliceAndBobOrCharlie)
|
||||
}
|
||||
@ -216,7 +217,7 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun `composite key validation with graph cycle detection`() {
|
||||
fun `composite key validation with graph cycle detection`() = kryoSpecific<CompositeKeyTests>("Cycle exists in the object graph which is not currently supported in AMQP mode") {
|
||||
val key1 = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey).build() as CompositeKey
|
||||
val key2 = CompositeKey.Builder().addKeys(alicePublicKey, key1).build() as CompositeKey
|
||||
val key3 = CompositeKey.Builder().addKeys(alicePublicKey, key2).build() as CompositeKey
|
||||
@ -330,10 +331,10 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
||||
|
||||
// Create self sign CA.
|
||||
val caKeyPair = Crypto.generateKeyPair()
|
||||
val ca = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test CA"), caKeyPair)
|
||||
val ca = X509Utilities.createSelfSignedCACertificate(getX500Name(CN = "Test CA", O = "R3", L = "London", C = "GB"), caKeyPair)
|
||||
|
||||
// Sign the composite key with the self sign CA.
|
||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca, caKeyPair, X500Name("CN=CompositeKey"), compositeKey)
|
||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca, caKeyPair, getX500Name(CN = "CompositeKey", O = "R3", L = "London", C = "GB"), compositeKey)
|
||||
|
||||
// Store certificate to keystore.
|
||||
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.internal.toTypedArray
|
||||
import net.corda.core.utilities.cert
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import net.corda.node.utilities.*
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
@ -18,13 +20,13 @@ class X509NameConstraintsTest {
|
||||
|
||||
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
|
||||
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(getX509Name("Corda Root CA", "London", "demo@r3.com", null), rootKeys)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(getX500Name(CN = "Corda Root CA", O = "R3CEV", L = "London", C = "GB"), rootKeys)
|
||||
|
||||
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, getX509Name("Corda Intermediate CA", "London", "demo@r3.com", null), intermediateCAKeyPair.public)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, getX500Name(CN = "Corda Intermediate CA", O = "R3CEV", L = "London", C = "GB"), intermediateCAKeyPair.public)
|
||||
|
||||
val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, getX509Name("Corda Client CA", "London", "demo@r3.com", null), clientCAKeyPair.public, nameConstraints = nameConstraints)
|
||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, getX500Name(CN = "Corda Client CA", O = "R3CEV", L = "London", C = "GB"), clientCAKeyPair.public, nameConstraints = nameConstraints)
|
||||
|
||||
val keyPass = "password"
|
||||
val trustStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
|
@ -56,6 +56,11 @@ class AttachmentTests {
|
||||
val nodes = mockNet.createSomeNodes(2)
|
||||
val n0 = nodes.partyNodes[0]
|
||||
val n1 = nodes.partyNodes[1]
|
||||
|
||||
// Ensure that registration was successful before progressing any further
|
||||
mockNet.runNetwork()
|
||||
n0.ensureRegistered()
|
||||
|
||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
|
||||
@ -89,6 +94,11 @@ class AttachmentTests {
|
||||
val nodes = mockNet.createSomeNodes(2)
|
||||
val n0 = nodes.partyNodes[0]
|
||||
val n1 = nodes.partyNodes[1]
|
||||
|
||||
// Ensure that registration was successful before progressing any further
|
||||
mockNet.runNetwork()
|
||||
n0.ensureRegistered()
|
||||
|
||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
|
||||
@ -119,6 +129,10 @@ class AttachmentTests {
|
||||
}, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type)))
|
||||
val n1 = mockNet.createNode(n0.network.myAddress)
|
||||
|
||||
// Ensure that registration was successful before progressing any further
|
||||
mockNet.runNetwork()
|
||||
n0.ensureRegistered()
|
||||
|
||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
|
||||
|
@ -36,6 +36,7 @@ class CollectSignaturesFlowTests {
|
||||
c = nodes.partyNodes[2]
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
mockNet.runNetwork()
|
||||
a.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
@ -140,9 +141,13 @@ class CollectSignaturesFlowTests {
|
||||
|
||||
@Test
|
||||
fun `successfully collects two signatures`() {
|
||||
val bConfidentialIdentity = b.services.keyManagementService.freshKeyAndCert(b.info.legalIdentityAndCert, false)
|
||||
// Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity
|
||||
a.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity)
|
||||
val bConfidentialIdentity = b.database.transaction {
|
||||
b.services.keyManagementService.freshKeyAndCert(b.info.legalIdentityAndCert, false)
|
||||
}
|
||||
a.database.transaction {
|
||||
// Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity
|
||||
a.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity)
|
||||
}
|
||||
registerFlowOnAllNodes(TestFlowTwo.Responder::class)
|
||||
val magicNumber = 1337
|
||||
val parties = listOf(a.info.legalIdentity, bConfidentialIdentity.party, c.info.legalIdentity)
|
||||
|
@ -46,11 +46,21 @@ class ContractUpgradeFlowTest {
|
||||
val nodes = mockNet.createSomeNodes(notaryKeyPair = null) // prevent generation of notary override
|
||||
a = nodes.partyNodes[0]
|
||||
b = nodes.partyNodes[1]
|
||||
|
||||
// Process registration
|
||||
mockNet.runNetwork()
|
||||
a.ensureRegistered()
|
||||
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
|
||||
val nodeIdentity = nodes.notaryNode.info.legalIdentitiesAndCerts.single { it.party == nodes.notaryNode.info.notaryIdentity }
|
||||
a.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
a.database.transaction {
|
||||
a.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
}
|
||||
b.database.transaction {
|
||||
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
@ -74,15 +84,24 @@ class ContractUpgradeFlowTest {
|
||||
requireNotNull(btx)
|
||||
|
||||
// The request is expected to be rejected because party B hasn't authorised the upgrade yet.
|
||||
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade.
|
||||
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||
// Party B authorise the contract state upgrade, and immediately deauthorise the same.
|
||||
b.services.startFlow(ContractUpgradeFlow.Authorise(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow()
|
||||
b.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).resultFuture.getOrThrow()
|
||||
|
||||
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
|
||||
val deauthorisedFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade
|
||||
b.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow()
|
||||
|
||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
||||
val resultFuture = a.services.startFlow(ContractUpgradeFlow(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
|
||||
val result = resultFuture.getOrThrow()
|
||||
@ -128,7 +147,10 @@ class ContractUpgradeFlowTest {
|
||||
|
||||
val user = rpcTestUser.copy(permissions = setOf(
|
||||
startFlowPermission<FinalityInvoker>(),
|
||||
startFlowPermission<ContractUpgradeFlow<*, *>>()
|
||||
startFlowPermission<ContractUpgradeFlow.Initiator<*, *>>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Acceptor>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Authorise>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Deauthorise>()
|
||||
))
|
||||
val rpcA = startProxy(a, user)
|
||||
val rpcB = startProxy(b, user)
|
||||
@ -141,18 +163,35 @@ class ContractUpgradeFlowTest {
|
||||
requireNotNull(atx)
|
||||
requireNotNull(btx)
|
||||
|
||||
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
|
||||
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
atx!!.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade, and immediately deauthorise the same.
|
||||
rpcB.startFlow( { stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade ) },
|
||||
btx!!.tx.outRef<ContractState>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
rpcB.startFlow( { stateRef -> ContractUpgradeFlow.Deauthorise(stateRef) },
|
||||
btx.tx.outRef<ContractState>(0).ref).returnValue
|
||||
|
||||
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
|
||||
val deauthorisedFuture = rpcA.startFlow( {stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
atx.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade.
|
||||
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||
rpcB.startFlow( { stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade ) },
|
||||
btx.tx.outRef<ContractState>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
||||
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
|
||||
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
atx.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
@ -184,7 +223,7 @@ class ContractUpgradeFlowTest {
|
||||
val baseState = a.database.transaction { a.services.vaultQueryService.queryBy<ContractState>().states.single() }
|
||||
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
||||
// Starts contract upgrade flow.
|
||||
val upgradeResult = a.services.startFlow(ContractUpgradeFlow(stateAndRef, CashV2::class.java)).resultFuture
|
||||
val upgradeResult = a.services.startFlow(ContractUpgradeFlow.Initiator(stateAndRef, CashV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
upgradeResult.getOrThrow()
|
||||
// Get contract state from the vault.
|
||||
|
@ -29,6 +29,7 @@ class FinalityFlowTests {
|
||||
nodeB = nodes.partyNodes[1]
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
mockNet.runNetwork()
|
||||
nodeA.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -41,10 +41,6 @@ class IdentitySyncFlowTests {
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
||||
val bob: Party = bobNode.services.myInfo.legalIdentity
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
bobNode.registerInitiatedFlow(Receive::class.java)
|
||||
|
||||
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
|
||||
@ -53,12 +49,16 @@ class IdentitySyncFlowTests {
|
||||
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notaryNode.services.myInfo.notaryIdentity))
|
||||
val issueTx = issueFlow.resultFuture.getOrThrow().stx
|
||||
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
|
||||
assertNull(bobNode.services.identityService.partyFromAnonymous(confidentialIdentity))
|
||||
assertNull(bobNode.database.transaction { bobNode.services.identityService.partyFromAnonymous(confidentialIdentity) })
|
||||
|
||||
// Run the flow to sync up the identities
|
||||
aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).resultFuture.getOrThrow()
|
||||
val expected = aliceNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
val actual = bobNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
val expected = aliceNode.database.transaction {
|
||||
aliceNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
}
|
||||
val actual = bobNode.database.transaction {
|
||||
bobNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
}
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ class ManualFinalityFlowTests {
|
||||
nodeC = nodes.partyNodes[2]
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
mockNet.runNetwork()
|
||||
nodeA.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -26,10 +26,6 @@ class TransactionKeyFlowTests {
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
||||
val bob: Party = bobNode.services.myInfo.legalIdentity
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
|
||||
// Run the flows
|
||||
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
|
||||
@ -44,8 +40,8 @@ class TransactionKeyFlowTests {
|
||||
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity)
|
||||
|
||||
// Verify that the anonymous identities look sane
|
||||
assertEquals(alice.name, aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name)
|
||||
assertEquals(bob.name, bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name)
|
||||
assertEquals(alice.name, aliceNode.database.transaction { aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name })
|
||||
assertEquals(bob.name, bobNode.database.transaction { bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name })
|
||||
|
||||
// Verify that the nodes have the right anonymous identities
|
||||
assertTrue { aliceAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
|
||||
|
@ -3,10 +3,10 @@ package net.corda.core.identity
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import net.corda.testing.getTestPartyAndCertificate
|
||||
import net.corda.testing.withTestSerialization
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
|
||||
@ -15,7 +15,7 @@ class PartyAndCertificateTest {
|
||||
fun `kryo serialisation`() {
|
||||
withTestSerialization {
|
||||
val original = getTestPartyAndCertificate(Party(
|
||||
X500Name("CN=Test Corp,O=Test Corp,L=Madrid,C=ES"),
|
||||
getX500Name(O = "Test Corp", L = "Madrid", C = "ES"),
|
||||
entropyToKeyPair(BigInteger.valueOf(83)).public))
|
||||
val copy = original.serialize().deserialize()
|
||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||
|
@ -0,0 +1,118 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.AfterClass
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AbstractAttachmentTest {
|
||||
companion object {
|
||||
private val dir = Files.createTempDirectory(AbstractAttachmentTest::class.simpleName)
|
||||
private val bin = Paths.get(System.getProperty("java.home")).let { if (it.endsWith("jre")) it.parent else it } / "bin"
|
||||
private val shredder = (dir / "_shredder").toFile() // No need to delete after each test.
|
||||
fun execute(vararg command: String) {
|
||||
assertEquals(0, ProcessBuilder()
|
||||
.inheritIO()
|
||||
.redirectOutput(shredder)
|
||||
.directory(dir.toFile())
|
||||
.command((bin / command[0]).toString(), *command.sliceArray(1 until command.size))
|
||||
.start()
|
||||
.waitFor())
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun beforeClass() {
|
||||
execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", "alice", "-keypass", "alicepass", "-dname", ALICE.toString())
|
||||
execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", "bob", "-keypass", "bobpass", "-dname", BOB.toString())
|
||||
(dir / "_signable1").writeLines(listOf("signable1"))
|
||||
(dir / "_signable2").writeLines(listOf("signable2"))
|
||||
(dir / "_signable3").writeLines(listOf("signable3"))
|
||||
}
|
||||
|
||||
private fun load(name: String) = object : AbstractAttachment({ (dir / name).readAll() }) {
|
||||
override val id get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic
|
||||
fun afterClass() {
|
||||
dir.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
dir.toFile().listFiles().forEach {
|
||||
if (!it.name.startsWith("_")) it.deleteRecursively()
|
||||
}
|
||||
assertEquals(5, dir.toFile().listFiles().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty jar has no signers`() {
|
||||
(dir / "META-INF").createDirectory() // At least one arg is required, and jar cvf conveniently ignores this.
|
||||
execute("jar", "cvf", "attachment.jar", "META-INF")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers) // There needs to have been a file for ALICE to sign.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unsigned jar has no signers`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||
execute("jar", "uvf", "attachment.jar", "_signable2")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `one signer`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1", "_signable2")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name }) // We only reused ALICE's distinguished name, so the keys will be different.
|
||||
(dir / "my-dir").createDirectory()
|
||||
execute("jar", "uvf", "attachment.jar", "my-dir")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name }) // Unsigned directory is irrelevant.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two signers`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1", "_signable2")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||
assertEquals(listOf(ALICE.name, BOB.name), load("attachment.jar").signers.map { it.name })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a party must sign all the files in the attachment to be a signer`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name })
|
||||
execute("jar", "uvf", "attachment.jar", "_signable2")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||
assertEquals(listOf(BOB.name), load("attachment.jar").signers.map { it.name }) // ALICE hasn't signed the new file.
|
||||
execute("jar", "uvf", "attachment.jar", "_signable3")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers) // Neither party has signed the new file.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bad signature is caught even if the party would not qualify as a signer`() {
|
||||
(dir / "volatile").writeLines(listOf("volatile"))
|
||||
execute("jar", "cvf", "attachment.jar", "volatile")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name })
|
||||
(dir / "volatile").writeLines(listOf("garbage"))
|
||||
execute("jar", "uvf", "attachment.jar", "volatile", "_signable1") // ALICE's signature on volatile is now bad.
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||
val a = load("attachment.jar")
|
||||
// The JDK doesn't care that BOB has correctly signed the whole thing, it won't let us process the entry with ALICE's bad signature:
|
||||
assertThatThrownBy { a.signers }.isInstanceOf(SecurityException::class.java)
|
||||
}
|
||||
}
|
@ -112,6 +112,18 @@ class CordaFutureTest {
|
||||
}
|
||||
verify(log).error(any(), same(throwable))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `captureLater works`() {
|
||||
val failingFuture = CordaFutureImpl<Int>()
|
||||
val anotherFailingFuture = CordaFutureImpl<Int>()
|
||||
anotherFailingFuture.captureLater(failingFuture)
|
||||
|
||||
val exception = Exception()
|
||||
failingFuture.setException(exception)
|
||||
|
||||
Assertions.assertThatThrownBy { anotherFailingFuture.getOrThrow() }.isSameAs(exception)
|
||||
}
|
||||
}
|
||||
|
||||
class TransposeTest {
|
||||
|
@ -2,14 +2,14 @@ package net.corda.core.node
|
||||
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.testing.getTestX509Name
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class ServiceInfoTests {
|
||||
val serviceType = ServiceType.getServiceType("test", "service").getSubType("subservice")
|
||||
val name = getTestX509Name("service.name")
|
||||
val name = getX500Name(O = "service.name", L = "London", C = "GB")
|
||||
|
||||
@Test
|
||||
fun `type and name encodes correctly`() {
|
||||
|
@ -46,13 +46,13 @@ private fun MockNetwork.MockNode.saveAttachment(content: String) = database.tran
|
||||
attachments.importAttachment(createAttachmentData(content).inputStream())
|
||||
}
|
||||
private fun MockNetwork.MockNode.hackAttachment(attachmentId: SecureHash, content: String) = database.transaction {
|
||||
attachments.updateAttachment(attachmentId, createAttachmentData(content))
|
||||
updateAttachment(attachmentId, createAttachmentData(content))
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeAttachmentService.importAttachment
|
||||
*/
|
||||
private fun NodeAttachmentService.updateAttachment(attachmentId: SecureHash, data: ByteArray) {
|
||||
private fun updateAttachment(attachmentId: SecureHash, data: ByteArray) {
|
||||
val session = DatabaseTransactionManager.current().session
|
||||
val attachment = session.get<NodeAttachmentService.DBAttachment>(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString())
|
||||
attachment?.let {
|
||||
@ -73,6 +73,7 @@ class AttachmentSerializationTest {
|
||||
client = mockNet.createNode(server.network.myAddress)
|
||||
client.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
|
||||
mockNet.runNetwork()
|
||||
server.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
@ -112,6 +113,7 @@ class AttachmentSerializationTest {
|
||||
|
||||
private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment {
|
||||
override fun open() = throw UnsupportedOperationException("Not implemented.")
|
||||
override val signers get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private class CustomAttachmentLogic(server: MockNetwork.MockNode, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(server) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.crypto
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.crypto.AddressFormatException
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
@ -52,8 +52,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
private class CapturingTransientProperty(prefix: String) {
|
||||
private val seed = random63BitValue()
|
||||
private class CapturingTransientProperty(val prefix: String, val seed: Long = random63BitValue()) {
|
||||
val transientVal by transient { prefix + seed + random63BitValue() }
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.utilities
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
@ -99,4 +100,33 @@ class LegalNameValidatorTest {
|
||||
validateLegalName("Legal Name With\n\rLine\nBreaks")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validate x500Name`() {
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US, CN=Service Name"))
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US"))
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US"))
|
||||
|
||||
// Missing Organisation
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Missing Locality
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=Bank A, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Missing Country
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Wrong organisation name format
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Wrong organisation name format
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,18 @@ dokka {
|
||||
moduleName = 'corda'
|
||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin")
|
||||
processConfigurations = ['compile']
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node-api/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
jdkVersion = 8
|
||||
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://fasterxml.github.io/jackson-core/javadoc/2.8/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://docs.oracle.com/javafx/2/api/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://www.bouncycastle.org/docs/docs1.5on/")
|
||||
}
|
||||
}
|
||||
|
||||
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
@ -17,8 +28,19 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
outputFormat = "javadoc"
|
||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
|
||||
processConfigurations = ['compile']
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node-api/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
includes = ['packages.md']
|
||||
jdkVersion = 8
|
||||
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://fasterxml.github.io/jackson-core/javadoc/2.8/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://docs.oracle.com/javafx/2/api/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://www.bouncycastle.org/docs/docs1.5on/")
|
||||
}
|
||||
}
|
||||
|
||||
task buildDocs(dependsOn: ['apidocs', 'makeDocs'])
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user