Merge pull request #35 from corda/aslemmer-enterprise-merge-september-8

Aslemmer enterprise merge september 8
This commit is contained in:
Andras Slemmer 2017-09-08 17:30:58 +01:00 committed by GitHub
commit fdc73571e5
463 changed files with 7165 additions and 3523 deletions

8
.github/ISSUE_TEMPLATE.md vendored Normal file
View 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
View 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
View File

@ -16,6 +16,7 @@ local.properties
**/build/*
lib/dokka.jar
lib/quasar.jar
**/logs/*

21
.idea/compiler.xml generated
View File

@ -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" />

View File

@ -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')
}
}
}

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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!"))
}
}
}

View File

@ -50,6 +50,7 @@ dependencies {
// Integration test helpers
integrationTestCompile "junit:junit:$junit_version"
integrationTestCompile project(':node-driver')
}
task integrationTest(type: Test) {

View File

@ -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()

View File

@ -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
})
}

View File

@ -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]
}

View File

@ -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

View File

@ -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 }
)
}
}

View File

@ -1,3 +1,4 @@
@file:JvmName("ObservableFold")
package net.corda.client.jfx.utils
import javafx.application.Platform

View File

@ -1,3 +1,4 @@
@file:JvmName("ObservableUtilities")
package net.corda.client.jfx.utils
import javafx.application.Platform

View File

@ -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
}
}
}

View File

@ -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&lt;B&gt; 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
}
}
}
}

View File

@ -1,3 +1,4 @@
@file:JvmName("Generators")
package net.corda.client.mock
import net.corda.core.contracts.Amount

View File

@ -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!

View File

@ -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" }
}
}
}
}

View File

@ -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(),

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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
}

View File

@ -1,3 +1,4 @@
@file:JvmName("ConcurrencyUtils")
package net.corda.core.concurrent
import net.corda.core.internal.concurrent.openFuture

View File

@ -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

View 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>
}

View File

@ -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

View File

@ -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.

View File

@ -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()

View File

@ -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")
}

View File

@ -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")

View File

@ -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")
}

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

View 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))
}

View File

@ -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)

View File

@ -11,4 +11,3 @@ import net.corda.core.serialization.CordaSerializable
*/
@CordaSerializable
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)

View File

@ -29,4 +29,5 @@ data class SignatureScheme(
val signatureName: String,
val algSpec: AlgorithmParameterSpec?,
val keySize: Int?,
val desc: String)
val desc: String
)

View File

@ -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)

View File

@ -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))

View File

@ -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")

View File

@ -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) {

View File

@ -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())
}
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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,

View File

@ -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.
}
}
}

View File

@ -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] }
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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 }

View File

@ -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)

View File

@ -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].

View File

@ -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.*

View 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>()
}
}
}

View File

@ -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
}
}

View File

@ -1,6 +1,6 @@
package net.corda.core.serialization
interface SerializationCustomization {
fun addToWhitelist(type: Class<*>)
fun addToWhitelist(vararg types: Class<*>)
}

View File

@ -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" }
}
}

View File

@ -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. */

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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 */

View File

@ -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.
*

View 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"
)

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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()

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -29,6 +29,7 @@ class FinalityFlowTests {
nodeB = nodes.partyNodes[1]
notary = nodes.notaryNode.info.notaryIdentity
mockNet.runNetwork()
nodeA.ensureRegistered()
}
@After

View File

@ -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)
}

View File

@ -32,6 +32,7 @@ class ManualFinalityFlowTests {
nodeC = nodes.partyNodes[2]
notary = nodes.notaryNode.info.notaryIdentity
mockNet.runNetwork()
nodeA.ensureRegistered()
}
@After

View File

@ -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 }

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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`() {

View File

@ -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) {

View File

@ -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

View File

@ -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() }
}
}

View File

@ -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"))
}
}
}

View File

@ -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