mirror of
https://github.com/corda/corda.git
synced 2025-02-05 10:39:13 +00:00
Merge pull request #35 from corda/aslemmer-enterprise-merge-september-8
Aslemmer enterprise merge september 8
This commit is contained in:
commit
fdc73571e5
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Thank you for choosing to report an issue with Corda.
|
||||||
|
|
||||||
|
When reporting an issue please make sure it contains;
|
||||||
|
|
||||||
|
* A clear description of the issue
|
||||||
|
* Any logs or stack traces that you can provide (use a site like https://pastebin.com for larger logs)
|
||||||
|
* Steps to reproduce the issue
|
||||||
|
* The version/tag/release or commit hash it occured on
|
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Thank you for choosing to contribute to Corda.
|
||||||
|
|
||||||
|
Your PR must be approved by one or more reviewers and all tests must be passed on TeamCity (https://ci.corda.r3cev.com) in order to be merged.
|
||||||
|
|
||||||
|
Once you have submitted a PR you are responsible for keeping it up to date until the time it is merged.
|
||||||
|
|
||||||
|
PR Checklist:
|
||||||
|
|
||||||
|
1. Ensure any new code is tested as described in https://docs.corda.net/testing.html
|
||||||
|
2. Ensure you have done any relevant automated testing and manual testing
|
||||||
|
3. Add your changes to docs/source/changelog.rst
|
||||||
|
4. Update any documentation in docs/source relating to your changes and learn how to build them in https://docs.corda.net/building-the-docs.html
|
||||||
|
5. If you are contributing for the first time please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you have read, and agreed to, the agreement.
|
||||||
|
|
||||||
|
Please remove this message when you have read it.
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ local.properties
|
|||||||
**/build/*
|
**/build/*
|
||||||
|
|
||||||
lib/dokka.jar
|
lib/dokka.jar
|
||||||
|
lib/quasar.jar
|
||||||
|
|
||||||
**/logs/*
|
**/logs/*
|
||||||
|
|
||||||
|
21
.idea/compiler.xml
generated
21
.idea/compiler.xml
generated
@ -62,8 +62,9 @@
|
|||||||
<module name="node-api_test" target="1.8" />
|
<module name="node-api_test" target="1.8" />
|
||||||
<module name="node-capsule_main" target="1.6" />
|
<module name="node-capsule_main" target="1.6" />
|
||||||
<module name="node-capsule_test" target="1.6" />
|
<module name="node-capsule_test" target="1.6" />
|
||||||
<module name="node-schemas_main" target="1.8" />
|
<module name="node-driver_integrationTest" target="1.8" />
|
||||||
<module name="node-schemas_test" 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_integrationTest" target="1.8" />
|
||||||
<module name="node_main" target="1.8" />
|
<module name="node_main" target="1.8" />
|
||||||
<module name="node_smokeTest" 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_integrationTest" target="1.8" />
|
||||||
<module name="simm-valuation-demo_main" target="1.8" />
|
<module name="simm-valuation-demo_main" target="1.8" />
|
||||||
<module name="simm-valuation-demo_test" target="1.8" />
|
<module name="simm-valuation-demo_test" target="1.8" />
|
||||||
<module name="smoke-test-utils_main" target="1.8" />
|
<module name="testing-node-driver_integrationTest" target="1.8" />
|
||||||
<module name="smoke-test-utils_test" target="1.8" />
|
<module name="testing-node-driver_main" target="1.8" />
|
||||||
<module name="test-common_main" target="1.8" />
|
<module name="testing-node-driver_test" target="1.8" />
|
||||||
<module name="test-common_test" target="1.8" />
|
<module name="testing-smoke-test-utils_main" target="1.8" />
|
||||||
<module name="test-utils_integrationTest" target="1.8" />
|
<module name="testing-smoke-test-utils_test" target="1.8" />
|
||||||
<module name="test-utils_main" target="1.8" />
|
<module name="testing-test-common_main" target="1.8" />
|
||||||
<module name="test-utils_test" 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_main" target="1.8" />
|
||||||
<module name="tools_test" target="1.8" />
|
<module name="tools_test" target="1.8" />
|
||||||
<module name="trader-demo_integrationTest" target="1.8" />
|
<module name="trader-demo_integrationTest" target="1.8" />
|
||||||
|
18
build.gradle
18
build.gradle
@ -4,7 +4,7 @@ buildscript {
|
|||||||
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
||||||
|
|
||||||
// Our version: bump this on release.
|
// 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
|
// 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
|
// 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
|
ext.corda_platform_version = 1
|
||||||
@ -143,10 +143,6 @@ allprojects {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
// TODO: remove this once we eliminate Exposed
|
|
||||||
maven {
|
|
||||||
url 'https://dl.bintray.com/kotlin/exposed'
|
|
||||||
}
|
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,15 +216,15 @@ tasks.withType(Test) {
|
|||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
directory "./build/nodes"
|
directory "./build/nodes"
|
||||||
networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB"
|
networkMap "O=Controller,OU=corda,L=London,C=GB"
|
||||||
node {
|
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"]
|
advertisedServices = ["corda.notary.validating"]
|
||||||
p2pPort 10002
|
p2pPort 10002
|
||||||
cordapps = []
|
cordapps = []
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "CN=Bank A,O=R3,OU=corda,L=London,C=GB"
|
name "O=Bank A,OU=corda,L=London,C=GB"
|
||||||
advertisedServices = []
|
advertisedServices = []
|
||||||
p2pPort 10012
|
p2pPort 10012
|
||||||
rpcPort 10013
|
rpcPort 10013
|
||||||
@ -236,7 +232,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
cordapps = []
|
cordapps = []
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "CN=Bank B,O=R3,OU=corda,L=London,C=GB"
|
name "O=Bank B,OU=corda,L=London,C=GB"
|
||||||
advertisedServices = []
|
advertisedServices = []
|
||||||
p2pPort 10007
|
p2pPort 10007
|
||||||
rpcPort 10008
|
rpcPort 10008
|
||||||
@ -256,7 +252,7 @@ bintrayConfig {
|
|||||||
projectUrl = 'https://github.com/corda/corda'
|
projectUrl = 'https://github.com/corda/corda'
|
||||||
gpgSign = true
|
gpgSign = true
|
||||||
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
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 {
|
license {
|
||||||
name = 'Apache-2.0'
|
name = 'Apache-2.0'
|
||||||
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||||
@ -291,7 +287,7 @@ artifactory {
|
|||||||
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
||||||
}
|
}
|
||||||
defaults {
|
defaults {
|
||||||
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver')
|
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.jackson
|
package net.corda.client.jackson
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
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.ContractState
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.crypto.*
|
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.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
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.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
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 net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
@ -1,11 +1,11 @@
|
|||||||
package net.corda.jackson
|
package net.corda.client.jackson
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||||
import com.google.common.collect.HashMultimap
|
import com.google.common.collect.HashMultimap
|
||||||
import com.google.common.collect.Multimap
|
import com.google.common.collect.Multimap
|
||||||
import net.corda.jackson.StringToMethodCallParser.ParsedMethodCall
|
import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.jackson
|
package net.corda.client.jackson
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.jackson
|
package net.corda.client.jackson
|
||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
@ -50,6 +50,7 @@ dependencies {
|
|||||||
|
|
||||||
// Integration test helpers
|
// Integration test helpers
|
||||||
integrationTestCompile "junit:junit:$junit_version"
|
integrationTestCompile "junit:junit:$junit_version"
|
||||||
|
integrationTestCompile project(':node-driver')
|
||||||
}
|
}
|
||||||
|
|
||||||
task integrationTest(type: Test) {
|
task integrationTest(type: Test) {
|
||||||
|
@ -58,13 +58,13 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
|||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlowPermission<CashPaymentFlow>(),
|
||||||
startFlowPermission<CashExitFlow>())
|
startFlowPermission<CashExitFlow>())
|
||||||
)
|
)
|
||||||
val aliceNodeFuture = startNode(ALICE.name, rpcUsers = listOf(cashUser))
|
val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
|
||||||
val notaryNodeFuture = startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
val notaryNodeFuture = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
|
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
|
||||||
val notaryNodeHandle = notaryNodeFuture.getOrThrow()
|
val notaryNodeHandle = notaryNodeFuture.getOrThrow()
|
||||||
aliceNode = aliceNodeHandle.nodeInfo
|
aliceNode = aliceNodeHandle.nodeInfo
|
||||||
notaryNode = notaryNodeHandle.nodeInfo
|
notaryNode = notaryNodeHandle.nodeInfo
|
||||||
newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
|
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
|
||||||
val monitor = NodeMonitorModel()
|
val monitor = NodeMonitorModel()
|
||||||
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
|
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
|
||||||
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
|
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
|
||||||
@ -76,7 +76,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
|||||||
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false)
|
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false)
|
||||||
rpc = monitor.proxyObservable.value!!
|
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
|
bobNode = bobNodeHandle.nodeInfo
|
||||||
val monitorBob = NodeMonitorModel()
|
val monitorBob = NodeMonitorModel()
|
||||||
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
|
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
|
||||||
|
@ -3,25 +3,30 @@ package net.corda.client.jfx.model
|
|||||||
import javafx.beans.property.SimpleObjectProperty
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
import java.util.*
|
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.
|
* 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 {
|
class ExchangeRateModel {
|
||||||
val exchangeRate: ObservableValue<ExchangeRate> = SimpleObjectProperty<ExchangeRate>(object : ExchangeRate {
|
val exchangeRate: ObservableValue<ExchangeRate> = SimpleObjectProperty<ExchangeRate>(object : ExchangeRate() {
|
||||||
override fun rate(from: Currency, to: Currency) = 1.0
|
override fun rate(from: Currency, to: Currency) = BigDecimal.ONE
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package net.corda.client.jfx.model
|
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.beans.value.ObservableValue
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import javafx.collections.ObservableList
|
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.fold
|
||||||
import net.corda.client.jfx.utils.map
|
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.NodeInfo
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -29,6 +30,12 @@ class NetworkIdentityModel {
|
|||||||
|
|
||||||
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
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 parties: ObservableList<NodeInfo> = networkIdentities.filtered { !it.isCordaService() }
|
||||||
val notaries: ObservableList<NodeInfo> = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
|
val notaries: ObservableList<NodeInfo> = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
|
||||||
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
|
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
|
||||||
@ -38,8 +45,5 @@ class NetworkIdentityModel {
|
|||||||
return advertisedServices.any { it.info.type.isNetworkMap() || it.info.type.isNotary() }
|
return advertisedServices.any { it.info.type.isNetworkMap() || it.info.type.isNotary() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use Identity Service in service hub instead?
|
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]
|
||||||
fun lookup(publicKey: PublicKey): ObservableValue<NodeInfo?> = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey.keys.any { it == publicKey } }) {
|
|
||||||
it.legalIdentity.owningKey.keys.any { it == publicKey }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ class NodeMonitorModel {
|
|||||||
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
|
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
|
||||||
|
|
||||||
// Transactions
|
// Transactions
|
||||||
val (transactions, newTransactions) = proxy.verifiedTransactionsFeed()
|
val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed()
|
||||||
newTransactions.startWith(transactions).subscribe(transactionsSubject)
|
newTransactions.startWith(transactions).subscribe(transactionsSubject)
|
||||||
|
|
||||||
// SM -> TX mapping
|
// SM -> TX mapping
|
||||||
|
@ -29,7 +29,7 @@ object AmountBindings {
|
|||||||
return EasyBind.combine(observableCurrency, observableExchangeRate) { currency, exchangeRate ->
|
return EasyBind.combine(observableCurrency, observableExchangeRate) { currency, exchangeRate ->
|
||||||
Pair<Currency, (Amount<Currency>) -> Long>(
|
Pair<Currency, (Amount<Currency>) -> Long>(
|
||||||
currency,
|
currency,
|
||||||
{ (quantity, _, token) -> (exchangeRate.rate(token, currency) * quantity).toLong() }
|
{ amount -> exchangeRate.exchangeAmount(amount, currency).quantity }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
@file:JvmName("ObservableFold")
|
||||||
package net.corda.client.jfx.utils
|
package net.corda.client.jfx.utils
|
||||||
|
|
||||||
import javafx.application.Platform
|
import javafx.application.Platform
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
@file:JvmName("ObservableUtilities")
|
||||||
package net.corda.client.jfx.utils
|
package net.corda.client.jfx.utils
|
||||||
|
|
||||||
import javafx.application.Platform
|
import javafx.application.Platform
|
||||||
|
@ -81,9 +81,10 @@ open class ReadOnlyBackedObservableMapBase<K, A, B> : ObservableMap<K, A> {
|
|||||||
throw UnsupportedOperationException("remove() can't be called on ReadOnlyObservableMapBase")
|
throw UnsupportedOperationException("remove() can't be called on ReadOnlyObservableMapBase")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* Construct an object modelling the given change to an observed map.
|
||||||
fun <A, K> ObservableMap<K, A>.createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change<K, A> {
|
*/
|
||||||
|
fun createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change<K, A> {
|
||||||
return object : MapChangeListener.Change<K, A>(this) {
|
return object : MapChangeListener.Change<K, A>(this) {
|
||||||
override fun getKey() = key
|
override fun getKey() = key
|
||||||
override fun wasRemoved() = removedValue != null
|
override fun wasRemoved() = removedValue != null
|
||||||
@ -92,4 +93,4 @@ fun <A, K> ObservableMap<K, A>.createMapChange(key: K, removedValue: A?, addedVa
|
|||||||
override fun getValueAdded() = addedValue
|
override fun getValueAdded() = addedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ import java.util.*
|
|||||||
* [Generator.choice] picks a generator from the specified list and runs that.
|
* [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.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.combine] combines two generators of A and B with a function (A, B) -> C. Variants exist for other arities.
|
||||||
* [Generator.flatMap] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this
|
* [Generator.flatMap] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this
|
||||||
* function minimal as it may explode the stack, especially when using recursion.
|
* 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.
|
* 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.
|
* The above will generate a random list of animals.
|
||||||
*/
|
*/
|
||||||
class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
|
class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
|
||||||
|
|
||||||
// Functor
|
// Functor
|
||||||
fun <B> map(function: (A) -> B): Generator<B> =
|
fun <B> map(function: (A) -> B): Generator<B> =
|
||||||
Generator { generate(it).map(function) }
|
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) } }
|
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 {
|
companion object {
|
||||||
fun <A> pure(value: A) = Generator { Try.Success(value) }
|
fun <A> pure(value: A) = Generator { Try.Success(value) }
|
||||||
fun <A> impure(valueClosure: () -> A) = Generator { Try.Success(valueClosure()) }
|
fun <A> impure(valueClosure: () -> A) = Generator { Try.Success(valueClosure()) }
|
||||||
fun <A> fail(error: Exception) = Generator<A> { Try.Failure(error) }
|
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> choice(generators: List<Generator<A>>) = intRange(0, generators.size - 1).flatMap { generators[it] }
|
||||||
|
|
||||||
fun <A> success(generate: (SplittableRandom) -> A) = Generator { Try.Success(generate(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> {
|
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>>()
|
val ranges = mutableListOf<Pair<Double, Double>>()
|
||||||
var current = 0.0
|
var current = 0.0
|
||||||
generators.forEach {
|
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>> {
|
fun <A> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
|
||||||
val result = mutableListOf<A>()
|
val result = mutableListOf<A>()
|
||||||
for (generator in generators) {
|
for (generator in generators) {
|
||||||
@ -99,49 +126,29 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
|
|||||||
}
|
}
|
||||||
Try.Success(result)
|
Try.Success(result)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <A> Generator.Companion.frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
|
fun int() = Generator.success(SplittableRandom::nextInt)
|
||||||
|
fun long() = Generator.success(SplittableRandom::nextLong)
|
||||||
fun <A> Generator<A>.generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
|
fun bytes(size: Int): Generator<ByteArray> = Generator.success { random ->
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() }
|
ByteArray(size) { random.nextInt().toByte() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Generator.Companion.intRange(range: IntRange) = intRange(range.first, range.last)
|
fun intRange(range: IntRange) = intRange(range.first, range.last)
|
||||||
fun Generator.Companion.intRange(from: Int, to: Int): Generator<Int> = Generator.success {
|
fun intRange(from: Int, to: Int): Generator<Int> = Generator.success {
|
||||||
(from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
|
(from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Generator.Companion.longRange(range: LongRange) = longRange(range.first, range.last)
|
fun longRange(range: LongRange) = longRange(range.first, range.last)
|
||||||
fun Generator.Companion.longRange(from: Long, to: Long): Generator<Long> = Generator.success {
|
fun longRange(from: Long, to: Long): Generator<Long> = Generator.success {
|
||||||
(from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
|
(from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Generator.Companion.double() = Generator.success { it.nextDouble() }
|
fun double() = Generator.success { it.nextDouble() }
|
||||||
fun Generator.Companion.doubleRange(from: Double, to: Double): Generator<Double> = Generator.success {
|
fun doubleRange(from: Double, to: Double): Generator<Double> = Generator.success {
|
||||||
from + it.nextDouble() * (to - from)
|
from + it.nextDouble() * (to - from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Generator.Companion.char() = Generator {
|
fun char() = Generator {
|
||||||
val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16))
|
val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16))
|
||||||
if (Character.isValidCodePoint(codePoint)) {
|
if (Character.isValidCodePoint(codePoint)) {
|
||||||
return@Generator Try.Success(codePoint.toChar())
|
return@Generator Try.Success(codePoint.toChar())
|
||||||
@ -150,7 +157,7 @@ fun Generator.Companion.char() = Generator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Generator.Companion.string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map {
|
fun string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
it.forEach {
|
it.forEach {
|
||||||
builder.append(it)
|
builder.append(it)
|
||||||
@ -158,7 +165,7 @@ fun Generator.Companion.string(meanSize: Double = 16.0) = replicatePoisson(meanS
|
|||||||
builder.toString()
|
builder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <A> Generator.Companion.replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
|
fun <A> replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
|
||||||
val generators = mutableListOf<Generator<A>>()
|
val generators = mutableListOf<Generator<A>>()
|
||||||
for (i in 1..number) {
|
for (i in 1..number) {
|
||||||
generators.add(generator)
|
generators.add(generator)
|
||||||
@ -167,7 +174,7 @@ fun <A> Generator.Companion.replicate(number: Int, generator: Generator<A>): Gen
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun <A> Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator<List<A>> {
|
fun <A> replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator<List<A>> {
|
||||||
val chance = (meanSize - 1) / meanSize
|
val chance = (meanSize - 1) / meanSize
|
||||||
val result = mutableListOf<A>()
|
val result = mutableListOf<A>()
|
||||||
var finish = false
|
var finish = false
|
||||||
@ -189,8 +196,8 @@ fun <A> Generator.Companion.replicatePoisson(meanSize: Double, generator: Genera
|
|||||||
Try.Success(result)
|
Try.Success(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <A> Generator.Companion.pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
|
fun <A> 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>> {
|
fun <A> pickN(number: Int, list: List<A>) = Generator<List<A>> {
|
||||||
val mask = BitSet(list.size)
|
val mask = BitSet(list.size)
|
||||||
val size = Math.min(list.size, number)
|
val size = Math.min(list.size, number)
|
||||||
for (i in 0..size - 1) {
|
for (i in 0..size - 1) {
|
||||||
@ -212,11 +219,11 @@ fun <A> Generator.Companion.pickN(number: Int, list: List<A>) = Generator<List<A
|
|||||||
Try.Success(resultList)
|
Try.Success(resultList)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <A> Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
|
fun <A> sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
|
||||||
sampleBernoulli(listOf(collection), maxRatio)
|
sampleBernoulli(listOf(collection), maxRatio)
|
||||||
|
|
||||||
fun <A> Generator.Companion.sampleBernoulli(collection: Collection<A>, meanRatio: Double = 1.0): Generator<List<A>> =
|
fun <A> sampleBernoulli(collection: Collection<A>, meanRatio: Double = 1.0): Generator<List<A>> {
|
||||||
replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
|
return replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
|
||||||
val result = mutableListOf<A>()
|
val result = mutableListOf<A>()
|
||||||
collection.forEachIndexed { index, element ->
|
collection.forEachIndexed { index, element ->
|
||||||
if (chances[index] < meanRatio) {
|
if (chances[index] < meanRatio) {
|
||||||
@ -225,3 +232,7 @@ fun <A> Generator.Companion.sampleBernoulli(collection: Collection<A>, meanRatio
|
|||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
@file:JvmName("Generators")
|
||||||
package net.corda.client.mock
|
package net.corda.client.mock
|
||||||
|
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
|
@ -67,7 +67,7 @@ dependencies {
|
|||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
|
||||||
testCompile project(':test-utils')
|
testCompile project(':node-driver')
|
||||||
testCompile project(':client:mock')
|
testCompile project(':client:mock')
|
||||||
|
|
||||||
// Smoke tests do NOT have any Node code on the classpath!
|
// Smoke tests do NOT have any Node code on the classpath!
|
||||||
|
@ -82,6 +82,19 @@ class RPCClientProxyHandler(
|
|||||||
val log = loggerFor<RPCClientProxyHandler>()
|
val log = loggerFor<RPCClientProxyHandler>()
|
||||||
// To check whether toString() is being invoked
|
// To check whether toString() is being invoked
|
||||||
val toStringMethod: Method = Object::toString.javaMethod!!
|
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
|
// Used for reaping
|
||||||
@ -393,6 +406,19 @@ object RpcClientObservableSerializer : Serializer<Observable<*>>() {
|
|||||||
return serializationContext.withProperty(RpcObservableContextKey, observableContext)
|
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> {
|
override fun read(kryo: Kryo, input: Input, type: Class<Observable<*>>): Observable<Any> {
|
||||||
val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext
|
val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext
|
||||||
val observableId = RPCApi.ObservableId(input.readLong(true))
|
val observableId = RPCApi.ObservableId(input.readLong(true))
|
||||||
@ -405,7 +431,7 @@ object RpcClientObservableSerializer : Serializer<Observable<*>>() {
|
|||||||
observableContext.callSiteMap?.put(observableId.toLong, rpcCallSite)
|
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
|
// 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.
|
// 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
|
// 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.
|
// will arrive after the client unsubscribes from the observable and consequently invalidates the mapping.
|
||||||
// The unsubscribe is due to [ObservableToFuture]'s use of first().
|
// The unsubscribe is due to [ObservableToFuture]'s use of first().
|
||||||
@ -422,29 +448,3 @@ object RpcClientObservableSerializer : Serializer<Observable<*>>() {
|
|||||||
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
|
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) {
|
|
||||||
var currentThrowable = throwable
|
|
||||||
while (true) {
|
|
||||||
val cause = currentThrowable.cause
|
|
||||||
if (cause == null) {
|
|
||||||
currentThrowable.initCause(callSite)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
currentThrowable = cause
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> Observable<T>.pinInSubscriptions(hardReferenceStore: MutableSet<Observable<*>>): Observable<T> {
|
|
||||||
val refCount = AtomicInteger(0)
|
|
||||||
return this.doOnSubscribe {
|
|
||||||
if (refCount.getAndIncrement() == 0) {
|
|
||||||
require(hardReferenceStore.add(this)) { "Reference store already contained reference $this on add" }
|
|
||||||
}
|
|
||||||
}.doOnUnsubscribe {
|
|
||||||
if (refCount.decrementAndGet() == 0) {
|
|
||||||
require(hardReferenceStore.remove(this)) { "Reference store did not contain reference $this on remove" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6,12 +6,12 @@ import net.corda.core.messaging.CordaRPCOps;
|
|||||||
import net.corda.core.messaging.FlowHandle;
|
import net.corda.core.messaging.FlowHandle;
|
||||||
import net.corda.core.node.NodeInfo;
|
import net.corda.core.node.NodeInfo;
|
||||||
import net.corda.core.utilities.OpaqueBytes;
|
import net.corda.core.utilities.OpaqueBytes;
|
||||||
|
import net.corda.core.utilities.X500NameUtils;
|
||||||
import net.corda.finance.flows.AbstractCashFlow;
|
import net.corda.finance.flows.AbstractCashFlow;
|
||||||
import net.corda.finance.flows.CashIssueFlow;
|
import net.corda.finance.flows.CashIssueFlow;
|
||||||
import net.corda.nodeapi.User;
|
import net.corda.nodeapi.User;
|
||||||
import net.corda.smoketesting.NodeConfig;
|
import net.corda.smoketesting.NodeConfig;
|
||||||
import net.corda.smoketesting.NodeProcess;
|
import net.corda.smoketesting.NodeProcess;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -43,7 +43,7 @@ public class StandaloneCordaRPCJavaClientTest {
|
|||||||
private NodeInfo notaryNode;
|
private NodeInfo notaryNode;
|
||||||
|
|
||||||
private NodeConfig notaryConfig = new NodeConfig(
|
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(),
|
port.getAndIncrement(),
|
||||||
port.getAndIncrement(),
|
port.getAndIncrement(),
|
||||||
|
@ -9,10 +9,7 @@ import net.corda.core.messaging.*
|
|||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
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.*
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.core.utilities.seconds
|
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.POUNDS
|
import net.corda.finance.POUNDS
|
||||||
import net.corda.finance.SWISS_FRANCS
|
import net.corda.finance.SWISS_FRANCS
|
||||||
@ -26,7 +23,6 @@ import net.corda.nodeapi.User
|
|||||||
import net.corda.smoketesting.NodeConfig
|
import net.corda.smoketesting.NodeConfig
|
||||||
import net.corda.smoketesting.NodeProcess
|
import net.corda.smoketesting.NodeProcess
|
||||||
import org.apache.commons.io.output.NullOutputStream
|
import org.apache.commons.io.output.NullOutputStream
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -58,7 +54,7 @@ class StandaloneCordaRPClientTest {
|
|||||||
private lateinit var notaryNode: NodeInfo
|
private lateinit var notaryNode: NodeInfo
|
||||||
|
|
||||||
private val notaryConfig = NodeConfig(
|
private val notaryConfig = NodeConfig(
|
||||||
legalName = X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
|
legalName = getX500Name(O = "Notary Service", OU = "corda", L = "Zurich", C = "CH"),
|
||||||
p2pPort = port.andIncrement,
|
p2pPort = port.andIncrement,
|
||||||
rpcPort = port.andIncrement,
|
rpcPort = port.andIncrement,
|
||||||
webPort = port.andIncrement,
|
webPort = port.andIncrement,
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package net.corda.client.rpc
|
||||||
|
|
||||||
|
import com.esotericsoftware.kryo.KryoException
|
||||||
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
|
import net.corda.core.messaging.*
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.testing.rpcDriver
|
||||||
|
import net.corda.testing.startRpcClient
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RPCFailureTests {
|
||||||
|
class Unserializable
|
||||||
|
interface Ops : RPCOps {
|
||||||
|
fun getUnserializable(): Unserializable
|
||||||
|
fun getUnserializableAsync(): CordaFuture<Unserializable>
|
||||||
|
fun kotlinNPE()
|
||||||
|
fun kotlinNPEAsync(): CordaFuture<Unit>
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpsImpl : Ops {
|
||||||
|
override val protocolVersion = 1
|
||||||
|
override fun getUnserializable() = Unserializable()
|
||||||
|
override fun getUnserializableAsync(): CordaFuture<Unserializable> {
|
||||||
|
return openFuture<Unserializable>().apply { capture { getUnserializable() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun kotlinNPE() {
|
||||||
|
(null as Any?)!!.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun kotlinNPEAsync(): CordaFuture<Unit> {
|
||||||
|
return openFuture<Unit>().apply { capture { kotlinNPE() } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rpc(proc: (Ops) -> Any?): Unit = rpcDriver {
|
||||||
|
val server = startRpcServer(ops = OpsImpl()).getOrThrow()
|
||||||
|
proc(startRpcClient<Ops>(server.broker.hostAndPort!!).getOrThrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `kotlin NPE`() = rpc {
|
||||||
|
assertThatThrownBy { it.kotlinNPE() }.isInstanceOf(KotlinNullPointerException::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `kotlin NPE async`() = rpc {
|
||||||
|
val future = it.kotlinNPEAsync()
|
||||||
|
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KotlinNullPointerException::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unserializable`() = rpc {
|
||||||
|
assertThatThrownBy { it.getUnserializable() }.isInstanceOf(KryoException::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unserializable async`() = rpc {
|
||||||
|
val future = it.getUnserializableAsync()
|
||||||
|
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KryoException::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
myLegalName : "CN=Bank A,O=Bank A,L=London,C=GB"
|
myLegalName : "O=Bank A,L=London,C=GB"
|
||||||
keyStorePassword : "cordacadevpass"
|
keyStorePassword : "cordacadevpass"
|
||||||
trustStorePassword : "trustpass"
|
trustStorePassword : "trustpass"
|
||||||
p2pAddress : "localhost:10002"
|
p2pAddress : "localhost:10002"
|
||||||
@ -7,6 +7,6 @@ webAddress : "localhost:10004"
|
|||||||
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
|
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
|
||||||
networkMapService : {
|
networkMapService : {
|
||||||
address : "localhost:10000"
|
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
|
useHTTPS : false
|
||||||
|
@ -7,6 +7,6 @@ webAddress : "localhost:10007"
|
|||||||
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
|
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
|
||||||
networkMapService : {
|
networkMapService : {
|
||||||
address : "localhost:10000"
|
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
|
useHTTPS : false
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
myLegalName : "CN=Notary Service,O=R3,OU=corda,L=London,C=GB"
|
myLegalName : "O=Notary Service,OU=corda,L=London,C=GB"
|
||||||
keyStorePassword : "cordacadevpass"
|
keyStorePassword : "cordacadevpass"
|
||||||
trustStorePassword : "trustpass"
|
trustStorePassword : "trustpass"
|
||||||
p2pAddress : "localhost:10000"
|
p2pAddress : "localhost:10000"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
gradlePluginsVersion=0.15.1
|
gradlePluginsVersion=0.16.2
|
||||||
kotlinVersion=1.1.4
|
kotlinVersion=1.1.4
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
bouncycastleVersion=1.57
|
bouncycastleVersion=1.57
|
||||||
|
@ -22,12 +22,15 @@ dependencies {
|
|||||||
|
|
||||||
// Bring in the MockNode infrastructure for writing protocol unit tests.
|
// Bring in the MockNode infrastructure for writing protocol unit tests.
|
||||||
testCompile project(":node")
|
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-stdlib-jre8:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$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
|
// Thread safety annotations
|
||||||
compile "com.google.code.findbugs:jsr305:3.0.1"
|
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"
|
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 {
|
configurations {
|
||||||
testArtifacts.extendsFrom testRuntime
|
testArtifacts.extendsFrom testRuntime
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
@file:JvmName("ConcurrencyUtils")
|
||||||
package net.corda.core.concurrent
|
package net.corda.core.concurrent
|
||||||
|
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.core.contracts
|
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.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.exactAdd
|
import net.corda.core.utilities.exactAdd
|
||||||
|
42
core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
Normal file
42
core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package net.corda.core.contracts
|
||||||
|
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.extractFile
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
|
||||||
|
* contain public static data which can be referenced from transactions and utilised from contracts. Good examples
|
||||||
|
* of how attachments are meant to be used include:
|
||||||
|
* - Calendar data
|
||||||
|
* - Fixes (e.g. LIBOR)
|
||||||
|
* - Smart contract code
|
||||||
|
* - Legal documents
|
||||||
|
* - Facts generated by oracles which might be reused a lot
|
||||||
|
*/
|
||||||
|
interface Attachment : NamedByHash {
|
||||||
|
fun open(): InputStream
|
||||||
|
fun openAsJAR(): JarInputStream {
|
||||||
|
val stream = open()
|
||||||
|
try {
|
||||||
|
return JarInputStream(stream)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
stream.use { throw t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the named file case insensitively and copies it to the output stream.
|
||||||
|
* @throws FileNotFoundException if the given path doesn't exist in the attachment.
|
||||||
|
*/
|
||||||
|
fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parties that have correctly signed the whole attachment.
|
||||||
|
* Can be empty, for example non-contract attachments won't be necessarily be signed.
|
||||||
|
*/
|
||||||
|
val signers: List<Party>
|
||||||
|
}
|
@ -32,33 +32,33 @@ inline fun <R> requireThat(body: Requirements.() -> R) = Requirements.body()
|
|||||||
// TODO: Provide a version of select that interops with Java
|
// TODO: Provide a version of select that interops with Java
|
||||||
|
|
||||||
/** Filters the command list by type, party and public key all at once. */
|
/** 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) =
|
party: AbstractParty? = null) =
|
||||||
filter { it.value is T }.
|
filter { it.value is T }.
|
||||||
filter { if (signer == null) true else signer in it.signers }.
|
filter { if (signer == null) true else signer in it.signers }.
|
||||||
filter { if (party == null) true else party in it.signingParties }.
|
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
|
// TODO: Provide a version of select that interops with Java
|
||||||
|
|
||||||
/** Filters the command list by type, parties and public keys all at once. */
|
/** 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>?) =
|
parties: Collection<Party>?) =
|
||||||
filter { it.value is T }.
|
filter { it.value is T }.
|
||||||
filter { if (signers == null) true else it.signers.containsAll(signers) }.
|
filter { if (signers == null) true else it.signers.containsAll(signers) }.
|
||||||
filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
|
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. */
|
/** 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()
|
select<T>().single()
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
throw IllegalStateException("Required ${T::class.qualifiedName} command") // Better error message.
|
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. */
|
/** 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>) =
|
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>) =
|
||||||
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject<C> else null }.single()
|
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.
|
* 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)
|
@Throws(IllegalArgumentException::class)
|
||||||
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
||||||
commands: List<AuthenticatedObject<CommandData>>)
|
commands: List<CommandWithParties<CommandData>>)
|
||||||
: MoveCommand {
|
: MoveCommand {
|
||||||
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
|
// 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
|
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
||||||
|
@ -9,18 +9,11 @@ import net.corda.core.flows.FlowLogicRefFactory
|
|||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
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.serialization.serialize
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
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.security.PublicKey
|
||||||
import java.time.Instant
|
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). */
|
/** Implemented by anything that can be named by a secure hash value (e.g. transactions, attachments). */
|
||||||
interface NamedByHash {
|
interface NamedByHash {
|
||||||
@ -219,11 +212,6 @@ interface LinearState : ContractState {
|
|||||||
* except at issuance/termination.
|
* except at issuance/termination.
|
||||||
*/
|
*/
|
||||||
val linearId: UniqueIdentifier
|
val linearId: UniqueIdentifier
|
||||||
|
|
||||||
/**
|
|
||||||
* True if this should be tracked by our vault(s).
|
|
||||||
*/
|
|
||||||
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
|
|
||||||
}
|
}
|
||||||
// DOCEND 2
|
// DOCEND 2
|
||||||
|
|
||||||
@ -289,7 +277,7 @@ abstract class TypeOnlyCommandData : CommandData {
|
|||||||
data class Command<T : CommandData>(val value: T, val signers: List<PublicKey>) {
|
data class Command<T : CommandData>(val value: T, val signers: List<PublicKey>) {
|
||||||
// TODO Introduce NonEmptyList?
|
// TODO Introduce NonEmptyList?
|
||||||
init {
|
init {
|
||||||
require(signers.isNotEmpty())
|
require(signers.isNotEmpty()) { "The list of signers cannot be empty" }
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(data: T, key: PublicKey) : this(data, listOf(key))
|
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
|
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
|
||||||
|
|
||||||
// DOCSTART 6
|
// 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
|
@CordaSerializable
|
||||||
data class AuthenticatedObject<out T : Any>(
|
data class CommandWithParties<out T : CommandData>(
|
||||||
val signers: List<PublicKey>,
|
val signers: List<PublicKey>,
|
||||||
/** If any public keys were recognised, the looked up institutions are available here */
|
/** If any public keys were recognised, the looked up institutions are available here */
|
||||||
val signingParties: List<Party>,
|
val signingParties: List<Party>,
|
||||||
@ -367,69 +355,6 @@ interface UpgradedContract<in OldState : ContractState, out NewState : ContractS
|
|||||||
fun upgrade(state: OldState): NewState
|
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
|
* 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.
|
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
package net.corda.core.crypto.composite
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.CompositeKey.NodeAndWeight
|
||||||
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.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.utilities.exactAdd
|
||||||
import net.corda.core.utilities.sequence
|
import net.corda.core.utilities.sequence
|
||||||
import org.bouncycastle.asn1.*
|
import org.bouncycastle.asn1.*
|
||||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||||
@ -15,8 +11,10 @@ import java.security.PublicKey
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tree data structure that enables the representation of composite public keys.
|
* A tree data structure that enables the representation of composite public keys, which are used to represent
|
||||||
* Notice that with that implementation CompositeKey extends PublicKey. Leaves are represented by single public keys.
|
* 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
|
* For complex scenarios, such as *"Both Alice and Bob need to sign to consume a state S"*, we can represent
|
||||||
* the requirement by creating a tree with a root [CompositeKey], and Alice and Bob as children.
|
* the 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
|
* 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"*.
|
* 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.
|
* @property threshold specifies the minimum total weight required (in the simple case – the minimum number of child
|
||||||
*
|
|
||||||
* The [threshold] specifies the minimum total weight required (in the simple case – the minimum number of child
|
|
||||||
* signatures required) to satisfy the sub-tree rooted at this node.
|
* signatures required) to satisfy the sub-tree rooted at this node.
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@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))
|
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>
|
val leafKeys: Set<PublicKey>
|
||||||
get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
|
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.
|
* 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.
|
* 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 {
|
fun build(threshold: Int? = null): PublicKey {
|
||||||
|
require(threshold == null || threshold > 0)
|
||||||
val n = children.size
|
val n = children.size
|
||||||
return if (n > 1)
|
return if (n > 1)
|
||||||
CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children)
|
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,
|
// 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.
|
// as there are scenarios where developers expected the underlying key and its composite versions to be equivalent.
|
||||||
children.first().node
|
children.first().node
|
||||||
} else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
|
} else throw IllegalStateException("Trying to build CompositeKey without child nodes.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Expands all [CompositeKey]s present in PublicKey iterable to set of single [PublicKey]s.
|
|
||||||
* If an element of the set is a single PublicKey it gives just that key, if it is a [CompositeKey] it returns all leaf
|
|
||||||
* keys for that composite element.
|
|
||||||
*/
|
|
||||||
val Iterable<PublicKey>.expandedCompositeKeys: Set<PublicKey>
|
|
||||||
get() = flatMap { it.keys }.toSet()
|
|
@ -1,11 +1,14 @@
|
|||||||
package net.corda.core.crypto.composite
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import java.security.*
|
import java.security.*
|
||||||
import java.security.spec.InvalidKeySpecException
|
import java.security.spec.InvalidKeySpecException
|
||||||
import java.security.spec.KeySpec
|
import java.security.spec.KeySpec
|
||||||
import java.security.spec.X509EncodedKeySpec
|
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)
|
@Throws(InvalidKeySpecException::class)
|
||||||
override fun engineGeneratePrivate(keySpec: KeySpec): PrivateKey {
|
override fun engineGeneratePrivate(keySpec: KeySpec): PrivateKey {
|
||||||
@ -23,7 +26,7 @@ class KeyFactory : KeyFactorySpi() {
|
|||||||
|
|
||||||
@Throws(InvalidKeySpecException::class)
|
@Throws(InvalidKeySpecException::class)
|
||||||
override fun <T : KeySpec> engineGetKeySpec(key: Key, keySpec: Class<T>): T {
|
override fun <T : KeySpec> engineGetKeySpec(key: Key, keySpec: Class<T>): T {
|
||||||
// Only support [X509EncodedKeySpec].
|
// Only support X509EncodedKeySpec.
|
||||||
throw InvalidKeySpecException("Not implemented yet $key $keySpec")
|
throw InvalidKeySpecException("Not implemented yet $key $keySpec")
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.core.crypto.composite
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.security.*
|
import java.security.*
|
||||||
@ -27,6 +27,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
|
|||||||
return signatureState!!
|
return signatureState!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in inherited API")
|
||||||
@Throws(InvalidAlgorithmParameterException::class)
|
@Throws(InvalidAlgorithmParameterException::class)
|
||||||
override fun engineGetParameter(param: String?): Any {
|
override fun engineGetParameter(param: String?): Any {
|
||||||
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
|
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)
|
@Throws(InvalidAlgorithmParameterException::class)
|
||||||
override fun engineSetParameter(param: String?, value: Any?) {
|
override fun engineSetParameter(param: String?, value: Any?) {
|
||||||
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
|
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
|
@ -1,7 +1,5 @@
|
|||||||
package net.corda.core.crypto.provider
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.crypto.composite.CompositeKey
|
|
||||||
import net.corda.core.crypto.composite.CompositeSignature
|
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||||
import java.security.AccessController
|
import java.security.AccessController
|
||||||
import java.security.PrivilegedAction
|
import java.security.PrivilegedAction
|
||||||
@ -17,20 +15,22 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setup() {
|
private fun setup() {
|
||||||
put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.composite.KeyFactory")
|
put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.CompositeKeyFactory")
|
||||||
put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.composite.CompositeSignature")
|
put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.CompositeSignature")
|
||||||
|
|
||||||
val compositeKeyOID = CordaObjectIdentifier.COMPOSITE_KEY.id
|
val compositeKeyOID = CordaObjectIdentifier.COMPOSITE_KEY.id
|
||||||
put("Alg.Alias.KeyFactory.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
|
put("Alg.Alias.KeyFactory.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
|
||||||
put("Alg.Alias.KeyFactory.OID.$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 {
|
object CordaObjectIdentifier {
|
||||||
// UUID-based OID
|
// 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_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
|
||||||
@JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
|
@JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
|
||||||
}
|
}
|
@ -1,9 +1,5 @@
|
|||||||
package net.corda.core.crypto
|
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.corda.core.serialization.serialize
|
||||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||||
@ -201,7 +197,7 @@ object Crypto {
|
|||||||
private val providerMap: Map<String, Provider> = mapOf(
|
private val providerMap: Map<String, Provider> = mapOf(
|
||||||
BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(),
|
BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(),
|
||||||
CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(),
|
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 {
|
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
|
||||||
putAll(EdDSASecurityProvider())
|
putAll(EdDSASecurityProvider())
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
package net.corda.core.crypto
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.crypto.composite.CompositeKey
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.toBase58
|
||||||
|
import net.corda.core.utilities.toSHA256Bytes
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import net.corda.core.utilities.SgxSupport
|
import net.corda.core.utilities.SgxSupport
|
||||||
import java.security.*
|
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 SignatureException if signing is not possible due to malformed data or private key.
|
||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
|
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
|
||||||
fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature {
|
fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign))
|
||||||
return DigitalSignature(Crypto.doSign(this, bytesToSign))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
|
||||||
return DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to sign with a key pair.
|
* 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. */
|
/** 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()
|
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
|
||||||
|
|
||||||
val PublicKey.keys: Set<PublicKey> get() {
|
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
|
||||||
return if (this is CompositeKey) this.leafKeys
|
|
||||||
else setOf(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
|
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
|
||||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean {
|
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
|
||||||
return if (this is CompositeKey) this.isFulfilledBy(otherKeys)
|
|
||||||
else 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 {
|
fun PublicKey.containsAny(otherKeys: Iterable<PublicKey>): Boolean {
|
||||||
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
|
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
|
||||||
else this in otherKeys
|
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()
|
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.component1(): PrivateKey = this.private
|
||||||
|
|
||||||
operator fun KeyPair.component2(): PublicKey = this.public
|
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()
|
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.
|
* @param numOfBytes how many random bytes to output.
|
||||||
* @return a random [ByteArray].
|
* @return a random [ByteArray].
|
||||||
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
|
* @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.
|
* which should never happen and suggests an unusual JVM or non-standard Java library.
|
||||||
*/
|
*/
|
||||||
@Throws(NoSuchAlgorithmException::class)
|
@Throws(NoSuchAlgorithmException::class)
|
||||||
fun secureRandomBytes(numOfBytes: Int): ByteArray {
|
fun secureRandomBytes(numOfBytes: Int): ByteArray = newSecureRandom().generateSeed(numOfBytes)
|
||||||
return newSecureRandom().generateSeed(numOfBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a hack added because during deserialisation when no-param constructors are called sometimes default values
|
* This is a hack added because during deserialisation when no-param constructors are called sometimes default values
|
||||||
|
@ -6,8 +6,8 @@ import java.security.InvalidKeyException
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
|
|
||||||
// TODO: Is there a use-case for bare [DigitalSignature], or is everything a [DigitalSignature.WithKey]? If there's no
|
// 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]
|
// actual use-case, we should merge the with key version into the parent class. In that case CompositeSignatureWithKeys
|
||||||
// should be renamed to match.
|
// should be renamed to match.
|
||||||
/** A wrapper around a digital signature. */
|
/** A wrapper around a digital signature. */
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
|
@ -3,7 +3,7 @@ package net.corda.core.crypto
|
|||||||
import java.util.*
|
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
|
* See: https://en.wikipedia.org/wiki/Merkle_tree
|
||||||
*
|
*
|
||||||
|
22
core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
Normal file
22
core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package net.corda.core.crypto
|
||||||
|
|
||||||
|
import net.corda.core.identity.AnonymousParty
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
object NullKeys {
|
||||||
|
@CordaSerializable
|
||||||
|
object NullPublicKey : PublicKey, Comparable<PublicKey> {
|
||||||
|
override fun getAlgorithm() = "NULL"
|
||||||
|
override fun getEncoded() = byteArrayOf(0)
|
||||||
|
override fun getFormat() = "NULL"
|
||||||
|
override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
|
||||||
|
override fun toString() = "NULL_KEY"
|
||||||
|
}
|
||||||
|
|
||||||
|
val NULL_PARTY = AnonymousParty(NullPublicKey)
|
||||||
|
|
||||||
|
/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
|
||||||
|
val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
|
||||||
|
|
||||||
|
}
|
@ -12,7 +12,7 @@ import java.security.MessageDigest
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
|
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) {
|
class SHA256(bytes: ByteArray) : SecureHash(bytes) {
|
||||||
init {
|
init {
|
||||||
require(bytes.size == 32)
|
require(bytes.size == 32)
|
||||||
|
@ -11,4 +11,3 @@ import net.corda.core.serialization.CordaSerializable
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)
|
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)
|
||||||
|
|
||||||
|
@ -29,4 +29,5 @@ data class SignatureScheme(
|
|||||||
val signatureName: String,
|
val signatureName: String,
|
||||||
val algSpec: AlgorithmParameterSpec?,
|
val algSpec: AlgorithmParameterSpec?,
|
||||||
val keySize: Int?,
|
val keySize: Int?,
|
||||||
val desc: String)
|
val desc: String
|
||||||
|
)
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
@file:JvmName("X500NameUtils")
|
|
||||||
package net.corda.core.crypto
|
|
||||||
|
|
||||||
import net.corda.core.internal.toX509CertHolder
|
|
||||||
import org.bouncycastle.asn1.ASN1Encodable
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.bouncycastle.asn1.x500.X500NameBuilder
|
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
|
||||||
import java.security.KeyPair
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
|
|
||||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
|
||||||
*/
|
|
||||||
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
|
|
||||||
* adds one.
|
|
||||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
|
||||||
*/
|
|
||||||
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
|
|
||||||
*
|
|
||||||
* @param mutator a function to generate the new value from the previous one.
|
|
||||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
|
||||||
*/
|
|
||||||
private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
|
|
||||||
val builder = X500NameBuilder(BCStyle.INSTANCE)
|
|
||||||
var matched = false
|
|
||||||
this.rdNs.forEach { rdn ->
|
|
||||||
rdn.typesAndValues.forEach { typeAndValue ->
|
|
||||||
when (typeAndValue.type) {
|
|
||||||
BCStyle.CN -> {
|
|
||||||
matched = true
|
|
||||||
builder.addRDN(typeAndValue.type, mutator(typeAndValue.value))
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
builder.addRDN(typeAndValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require(matched) { "Input X.500 name must include a common name (CN) attribute: ${this}" }
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
|
|
||||||
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
|
|
||||||
val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
|
|
||||||
val X500Name.locationOrNull: String? get() = try {
|
|
||||||
location
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
|
|
||||||
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a distinguished name from the provided values.
|
|
||||||
*/
|
|
||||||
@JvmOverloads
|
|
||||||
fun getX509Name(myLegalName: String, nearestCity: String, email: String, country: String? = null): X500Name {
|
|
||||||
return X500NameBuilder(BCStyle.INSTANCE).let { builder ->
|
|
||||||
builder.addRDN(BCStyle.CN, myLegalName)
|
|
||||||
builder.addRDN(BCStyle.L, nearestCity)
|
|
||||||
country?.let { builder.addRDN(BCStyle.C, it) }
|
|
||||||
builder.addRDN(BCStyle.E, email)
|
|
||||||
builder.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
|
@ -1,21 +0,0 @@
|
|||||||
package net.corda.core.crypto.testing
|
|
||||||
|
|
||||||
import net.corda.core.crypto.SignatureMetadata
|
|
||||||
import net.corda.core.crypto.TransactionSignature
|
|
||||||
import net.corda.core.identity.AnonymousParty
|
|
||||||
import net.corda.core.serialization.CordaSerializable
|
|
||||||
import java.security.PublicKey
|
|
||||||
|
|
||||||
@CordaSerializable
|
|
||||||
object NullPublicKey : PublicKey, Comparable<PublicKey> {
|
|
||||||
override fun getAlgorithm() = "NULL"
|
|
||||||
override fun getEncoded() = byteArrayOf(0)
|
|
||||||
override fun getFormat() = "NULL"
|
|
||||||
override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
|
|
||||||
override fun toString() = "NULL_KEY"
|
|
||||||
}
|
|
||||||
|
|
||||||
val NULL_PARTY = AnonymousParty(NullPublicKey)
|
|
||||||
|
|
||||||
/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
|
|
||||||
val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
|
|
@ -138,6 +138,7 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
// We use Void? instead of Unit? as that's what you'd use in Java.
|
// We use Void? instead of Unit? as that's what you'd use in Java.
|
||||||
abstract class Acceptor<in T>(val otherSide: Party,
|
abstract class Acceptor<in T>(val otherSide: Party,
|
||||||
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
|
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
|
||||||
|
constructor(otherSide: Party) : this(otherSide, Acceptor.tracker())
|
||||||
companion object {
|
companion object {
|
||||||
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
||||||
object APPROVING : ProgressTracker.Step("State replacement approved")
|
object APPROVING : ProgressTracker.Step("State replacement approved")
|
||||||
|
@ -3,7 +3,7 @@ package net.corda.core.flows
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
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.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
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.
|
* @param stx a partially signed transaction received from your counter-party.
|
||||||
* @throws FlowException if the proposed transaction fails the checks.
|
* @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) {
|
@Suspendable private fun checkMySignatureRequired(stx: SignedTransaction, signingKey: PublicKey) {
|
||||||
require(signingKey in stx.tx.requiredSigningKeys) {
|
require(signingKey in stx.tx.requiredSigningKeys) {
|
||||||
|
@ -1,31 +1,105 @@
|
|||||||
package net.corda.core.flows
|
package net.corda.core.flows
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import java.security.PublicKey
|
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
|
* 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.
|
* 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
|
* 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.
|
* use the new updated state for future transactions.
|
||||||
*/
|
*/
|
||||||
|
object ContractUpgradeFlow {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@InitiatingFlow
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
|
class Initiator<OldState : ContractState, out NewState : ContractState>(
|
||||||
originalState: StateAndRef<OldState>,
|
originalState: StateAndRef<OldState>,
|
||||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
) : 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
@InitiatedBy(ContractUpgradeFlow.Initiator::class)
|
||||||
|
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun verify(tx: LedgerTransaction) {
|
fun verify(tx: LedgerTransaction) {
|
||||||
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||||
verify(
|
verify(tx.inputStates.single(),
|
||||||
tx.inputStates.single(),
|
|
||||||
tx.outputStates.single(),
|
tx.outputStates.single(),
|
||||||
tx.commandsOfType<UpgradeCommand>().single())
|
tx.commandsOfType<UpgradeCommand>().single())
|
||||||
}
|
}
|
||||||
@ -44,29 +118,29 @@ class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState
|
|||||||
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
|
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
@Suspendable
|
||||||
val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
|
@Throws(StateReplacementException::class)
|
||||||
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal<Class<out UpgradedContract<ContractState, *>>>) {
|
||||||
// TODO: We need a much faster way of finding our key in the transaction
|
// Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and
|
||||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
// verify outputs matches the proposed upgrade.
|
||||||
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
|
||||||
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
|
||||||
|
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
||||||
|
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
||||||
|
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||||
|
val proposedTx = stx.tx
|
||||||
|
val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
||||||
|
requireThat {
|
||||||
|
"The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
|
||||||
|
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
||||||
|
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||||
|
}
|
||||||
|
ContractUpgradeFlow.Acceptor.verify(
|
||||||
|
oldStateAndRef.state.data,
|
||||||
|
expectedTx.outRef<ContractState>(0).state.data,
|
||||||
|
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ object NotaryFlow {
|
|||||||
val tx: Any = if (stx.isNotaryChangeTransaction()) {
|
val tx: Any = if (stx.isNotaryChangeTransaction()) {
|
||||||
stx.notaryChangeTx
|
stx.notaryChangeTx
|
||||||
} else {
|
} else {
|
||||||
stx.tx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
|
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
|
||||||
}
|
}
|
||||||
sendAndReceiveWithRetry(notaryParty, tx)
|
sendAndReceiveWithRetry(notaryParty, tx)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package net.corda.core.identity
|
|||||||
|
|
||||||
import net.corda.core.contracts.PartyAndReference
|
import net.corda.core.contracts.PartyAndReference
|
||||||
import net.corda.core.crypto.Crypto
|
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 net.corda.core.utilities.OpaqueBytes
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.core.contracts.Attachment
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.serialization.MissingAttachmentsException
|
||||||
|
import net.corda.core.serialization.SerializeAsTokenContext
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.security.CodeSigner
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
|
|
||||||
|
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
|
||||||
|
companion object {
|
||||||
|
fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
|
||||||
|
return {
|
||||||
|
val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
|
||||||
|
(a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File> */
|
||||||
|
private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex()
|
||||||
|
private val shredder = ByteArray(1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val attachmentData: ByteArray by lazy(dataLoader)
|
||||||
|
override fun open(): InputStream = attachmentData.inputStream()
|
||||||
|
override val signers by lazy {
|
||||||
|
// Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":
|
||||||
|
var attachmentSigners: MutableSet<CodeSigner>? = null
|
||||||
|
openAsJAR().use { jar ->
|
||||||
|
while (true) {
|
||||||
|
val entry = jar.nextJarEntry ?: break
|
||||||
|
if (entry.isDirectory || unsignableEntryName.matches(entry.name)) continue
|
||||||
|
while (jar.read(shredder) != -1) { // Must read entry fully for codeSigners to be valid.
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
val entrySigners = entry.codeSigners ?: emptyArray()
|
||||||
|
attachmentSigners?.retainAll(entrySigners) ?: run { attachmentSigners = entrySigners.toMutableSet() }
|
||||||
|
if (attachmentSigners!!.isEmpty()) break // Performance short-circuit.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(attachmentSigners ?: emptySet<CodeSigner>()).map {
|
||||||
|
Party(it.signerCertPath.certificates[0].toX509CertHolder())
|
||||||
|
}.sortedBy { it.name.toString() } // Determinism.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
|
||||||
|
override fun hashCode() = id.hashCode()
|
||||||
|
override fun toString() = "${javaClass.simpleName}(id=$id)"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
|
||||||
|
fun String.norm() = toLowerCase().split('\\', '/') // XXX: Should this really be locale-sensitive?
|
||||||
|
val p = path.norm()
|
||||||
|
while (true) {
|
||||||
|
val e = nextJarEntry ?: break
|
||||||
|
if (!e.isDirectory && e.name.norm() == p) {
|
||||||
|
copyTo(outputTo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
closeEntry()
|
||||||
|
}
|
||||||
|
throw FileNotFoundException(path)
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.AbstractAttachment
|
|
||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.contracts.NamedByHash
|
import net.corda.core.contracts.NamedByHash
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
@ -123,11 +123,11 @@ interface CordaRPCOps : RPCOps {
|
|||||||
*
|
*
|
||||||
* Generic vault query function which takes a [QueryCriteria] object to define filters,
|
* Generic vault query function which takes a [QueryCriteria] object to define filters,
|
||||||
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
|
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
|
||||||
* and returns a [Vault.PageAndUpdates] object containing
|
* and returns a [DataFeed] object containing
|
||||||
* 1) a snapshot as a [Vault.Page] (described previously in [queryBy])
|
* 1) a snapshot as a [Vault.Page] (described previously in [CordaRPCOps.vaultQueryBy])
|
||||||
* 2) an [Observable] of [Vault.Update]
|
* 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).
|
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
|
||||||
*/
|
*/
|
||||||
// DOCSTART VaultTrackByAPI
|
// DOCSTART VaultTrackByAPI
|
||||||
@ -157,15 +157,21 @@ interface CordaRPCOps : RPCOps {
|
|||||||
// DOCEND VaultTrackAPIHelpers
|
// 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
|
@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.
|
* 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].
|
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
|
||||||
*/
|
*/
|
||||||
@RPCReturnsObservables
|
@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
|
* 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].
|
* result of running the flow. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
|
||||||
*/
|
*/
|
||||||
@RPCReturnsObservables
|
@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.
|
* 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
|
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.
|
* Returns the node's current time.
|
||||||
*/
|
*/
|
||||||
@ -257,7 +249,7 @@ interface CordaRPCOps : RPCOps {
|
|||||||
* complete with an exception if it is unable to.
|
* complete with an exception if it is unable to.
|
||||||
*/
|
*/
|
||||||
@RPCReturnsObservables
|
@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
|
// 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.
|
// the node's state locally and query that directly.
|
||||||
@ -299,6 +291,11 @@ interface CordaRPCOps : RPCOps {
|
|||||||
* @return the node info if available.
|
* @return the node info if available.
|
||||||
*/
|
*/
|
||||||
fun nodeIdentityFromParty(party: AbstractParty): NodeInfo?
|
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(),
|
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
|
* 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.
|
* 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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
flowConstructor: () -> R
|
flowConstructor: () -> R
|
||||||
): FlowHandle<T> = startFlowDynamic(R::class.java)
|
): 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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
flowConstructor: (A) -> R,
|
flowConstructor: (A) -> R,
|
||||||
arg0: A
|
arg0: A
|
||||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0)
|
): 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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
flowConstructor: (A, B) -> R,
|
flowConstructor: (A, B) -> R,
|
||||||
arg0: A,
|
arg0: A,
|
||||||
arg1: B
|
arg1: B
|
||||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1)
|
): 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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
flowConstructor: (A, B, C) -> R,
|
flowConstructor: (A, B, C) -> R,
|
||||||
arg0: A,
|
arg0: A,
|
||||||
@ -348,7 +345,7 @@ inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
|||||||
arg2: C
|
arg2: C
|
||||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2)
|
): 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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
flowConstructor: (A, B, C, D) -> R,
|
flowConstructor: (A, B, C, D) -> R,
|
||||||
arg0: A,
|
arg0: A,
|
||||||
@ -357,7 +354,7 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow
|
|||||||
arg3: D
|
arg3: D
|
||||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
): 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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
flowConstructor: (A, B, C, D, E) -> R,
|
flowConstructor: (A, B, C, D, E) -> R,
|
||||||
arg0: A,
|
arg0: A,
|
||||||
@ -367,7 +364,7 @@ inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startF
|
|||||||
arg4: E
|
arg4: E
|
||||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
|
): 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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
flowConstructor: (A, B, C, D, E, F) -> R,
|
flowConstructor: (A, B, C, D, E, F) -> R,
|
||||||
arg0: A,
|
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.
|
* Same again, except this time with progress-tracking enabled.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
inline fun <T : Any, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
inline fun <T, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||||
@Suppress("unused_parameter")
|
@Suppress("unused_parameter")
|
||||||
flowConstructor: () -> R
|
flowConstructor: () -> R
|
||||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java)
|
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java)
|
||||||
|
|
||||||
@Suppress("unused")
|
@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")
|
@Suppress("unused_parameter")
|
||||||
flowConstructor: (A) -> R,
|
flowConstructor: (A) -> R,
|
||||||
arg0: A
|
arg0: A
|
||||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0)
|
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0)
|
||||||
|
|
||||||
@Suppress("unused")
|
@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")
|
@Suppress("unused_parameter")
|
||||||
flowConstructor: (A, B) -> R,
|
flowConstructor: (A, B) -> R,
|
||||||
arg0: A,
|
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)
|
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1)
|
||||||
|
|
||||||
@Suppress("unused")
|
@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")
|
@Suppress("unused_parameter")
|
||||||
flowConstructor: (A, B, C) -> R,
|
flowConstructor: (A, B, C) -> R,
|
||||||
arg0: A,
|
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)
|
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2)
|
||||||
|
|
||||||
@Suppress("unused")
|
@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")
|
@Suppress("unused_parameter")
|
||||||
flowConstructor: (A, B, C, D) -> R,
|
flowConstructor: (A, B, C, D) -> R,
|
||||||
arg0: A,
|
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)
|
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||||
|
|
||||||
@Suppress("unused")
|
@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")
|
@Suppress("unused_parameter")
|
||||||
flowConstructor: (A, B, C, D, E) -> R,
|
flowConstructor: (A, B, C, D, E) -> R,
|
||||||
arg0: A,
|
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)
|
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
|
||||||
|
|
||||||
@Suppress("unused")
|
@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")
|
@Suppress("unused_parameter")
|
||||||
flowConstructor: (A, B, C, D, E, F) -> R,
|
flowConstructor: (A, B, C, D, E, F) -> R,
|
||||||
arg0: A,
|
arg0: A,
|
||||||
|
@ -57,16 +57,11 @@ data class FlowProgressHandleImpl<A>(
|
|||||||
|
|
||||||
// Remember to add @Throws to FlowProgressHandle.close() if this throws an exception.
|
// Remember to add @Throws to FlowProgressHandle.close() if this throws an exception.
|
||||||
override fun close() {
|
override fun close() {
|
||||||
progress.notUsed()
|
|
||||||
returnValue.cancel(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private copy of the version in client:rpc.
|
|
||||||
private fun <T> Observable<T>.notUsed() {
|
|
||||||
try {
|
try {
|
||||||
this.subscribe({}, {}).unsubscribe()
|
progress.subscribe({}, {}).unsubscribe()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Swallow any other exceptions as well.
|
// Swallow any other exceptions as well.
|
||||||
}
|
}
|
||||||
|
returnValue.cancel(false)
|
||||||
|
}
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ import net.corda.core.node.services.ServiceInfo
|
|||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
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.
|
* 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.
|
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class NodeInfo(val addresses: List<NetworkHostAndPort>,
|
data class NodeInfo(val addresses: List<NetworkHostAndPort>,
|
||||||
val legalIdentityAndCert: PartyAndCertificate, //TODO This field will be removed in future PR which gets rid of services.
|
// TODO After removing of services these two fields will be merged together and made NonEmptySet.
|
||||||
val legalIdentitiesAndCerts: NonEmptySet<PartyAndCertificate>,
|
val legalIdentityAndCert: PartyAndCertificate,
|
||||||
|
val legalIdentitiesAndCerts: Set<PartyAndCertificate>,
|
||||||
val platformVersion: Int,
|
val platformVersion: Int,
|
||||||
val advertisedServices: List<ServiceEntry> = emptyList(),
|
val advertisedServices: List<ServiceEntry> = emptyList(),
|
||||||
val worldMapLocation: WorldMapLocation? = null) {
|
val serial: Long
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
require(advertisedServices.none { it.identity == legalIdentityAndCert }) {
|
require(advertisedServices.none { it.identity == legalIdentityAndCert }) {
|
||||||
"Service identities must be different from node legal identity"
|
"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> {
|
fun serviceIdentities(type: ServiceType): List<Party> {
|
||||||
return advertisedServices.mapNotNull { if (it.info.type.isSubTypeOf(type)) it.identity.party else null }
|
return advertisedServices.mapNotNull { if (it.info.type.isSubTypeOf(type)) it.identity.party else null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses node's owner X500 name to infer the node's location. Used in Explorer in map view.
|
||||||
|
*/
|
||||||
|
fun getWorldMapLocation(): WorldMapLocation? {
|
||||||
|
val nodeOwnerLocation = legalIdentity.name.locality
|
||||||
|
return nodeOwnerLocation.let { CityDatabase[it] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.crypto.SignatureMetadata
|
|||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -46,6 +47,7 @@ interface ServiceHub : ServicesForResolution {
|
|||||||
val vaultService: VaultService
|
val vaultService: VaultService
|
||||||
val vaultQueryService: VaultQueryService
|
val vaultQueryService: VaultQueryService
|
||||||
val keyManagementService: KeyManagementService
|
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.
|
* 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
|
* 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.
|
* @param builder The TransactionBuilder to seal with the node's signature.
|
||||||
* Any existing signatures on the builder will be preserved.
|
* Any existing signatures on the builder will be preserved.
|
||||||
* @return Returns a SignedTransaction with the new node signature attached.
|
* @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 signedTransaction The [SignedTransaction] to which the signature will apply.
|
||||||
* @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
|
* @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
|
* 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))
|
createSignature(signedTransaction, publicKey, SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(publicKey).schemeNumberID))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to create an additional signature for an existing (partially) [SignedTransaction]
|
* 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.
|
* 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.
|
* @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 {
|
fun createSignature(signedTransaction: SignedTransaction): TransactionSignature {
|
||||||
return createSignature(signedTransaction, legalIdentityKey)
|
return createSignature(signedTransaction, legalIdentityKey)
|
||||||
@ -242,6 +248,36 @@ interface ServiceHub : ServicesForResolution {
|
|||||||
*/
|
*/
|
||||||
fun addSignature(signedTransaction: SignedTransaction): SignedTransaction = addSignature(signedTransaction, legalIdentityKey)
|
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.
|
* Exposes a JDBC connection (session) object using the currently configured database.
|
||||||
* Applications can use this to execute arbitrary SQL queries (native, direct, prepared, callable)
|
* Applications can use this to execute arbitrary SQL queries (native, direct, prepared, callable)
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.corda.core.node.services
|
||||||
|
|
||||||
|
import net.corda.core.contracts.StateRef
|
||||||
|
import net.corda.core.contracts.UpgradedContract
|
||||||
|
import net.corda.core.flows.ContractUpgradeFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [ContractUpgradeService] is responsible for securely upgrading contract state objects according to
|
||||||
|
* a specified and mutually agreed (amongst participants) contract version.
|
||||||
|
* See also [ContractUpgradeFlow] to understand the workflow associated with contract upgrades.
|
||||||
|
*/
|
||||||
|
interface ContractUpgradeService {
|
||||||
|
|
||||||
|
/** Get contracts we would be willing to upgrade the suggested contract to. */
|
||||||
|
fun getAuthorisedContractUpgrade(ref: StateRef): String?
|
||||||
|
|
||||||
|
/** Store authorised state ref and associated UpgradeContract class */
|
||||||
|
fun storeAuthorisedContractUpgrade(ref: StateRef, upgradedContractClass: Class<out UpgradedContract<*, *>>)
|
||||||
|
|
||||||
|
/** Remove a previously authorised state ref */
|
||||||
|
fun removeAuthorisedContractUpgrade(ref: StateRef)
|
||||||
|
}
|
@ -8,6 +8,7 @@ import net.corda.core.internal.randomOrNull
|
|||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -44,7 +45,7 @@ interface NetworkMapCache {
|
|||||||
/** Tracks changes to the network map cache */
|
/** Tracks changes to the network map cache */
|
||||||
val changed: Observable<MapChange>
|
val changed: Observable<MapChange>
|
||||||
/** Future to track completion of the NetworkMapService registration. */
|
/** 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
|
* 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?
|
fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo?
|
||||||
|
|
||||||
/** Look up the node info for a legal name. */
|
/** 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
|
* 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()}.")
|
"Your options are: ${notaryNodes.map { "\"${it.notaryIdentity.name}\"" }.joinToString()}.")
|
||||||
return notary.advertisedServices.any { it.info.type.isValidatingNotary() }
|
return notary.advertisedServices.any { it.info.type.isValidatingNotary() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all network map data from local node cache.
|
||||||
|
*/
|
||||||
|
fun clearNetworkMapCache()
|
||||||
}
|
}
|
||||||
|
@ -25,5 +25,3 @@ data class ServiceInfo(val type: ServiceType, val name: X500Name? = null) {
|
|||||||
|
|
||||||
override fun toString() = if (name != null) "$type|$name" else type.toString()
|
override fun toString() = if (name != null) "$type|$name" else type.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Iterable<ServiceInfo>.containsType(type: ServiceType) = any { it.type == type }
|
|
||||||
|
@ -30,6 +30,7 @@ class ServiceType private constructor(val id: String) {
|
|||||||
val regulator: ServiceType = corda.getSubType("regulator")
|
val regulator: ServiceType = corda.getSubType("regulator")
|
||||||
val networkMap: ServiceType = corda.getSubType("network_map")
|
val networkMap: ServiceType = corda.getSubType("network_map")
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
fun getServiceType(namespace: String, typeId: String): ServiceType {
|
fun getServiceType(namespace: String, typeId: String): ServiceType {
|
||||||
require(!namespace.startsWith("corda")) { "Corda namespace is protected" }
|
require(!namespace.startsWith("corda")) { "Corda namespace is protected" }
|
||||||
return baseWithSubType(namespace, typeId)
|
return baseWithSubType(namespace, typeId)
|
||||||
|
@ -173,17 +173,6 @@ interface VaultService {
|
|||||||
*/
|
*/
|
||||||
val updatesPublisher: PublishSubject<Vault.Update<ContractState>>
|
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.
|
* 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()
|
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
|
* Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id
|
||||||
* Multiple notes may be attached to the same [LedgerTransaction].
|
* Multiple notes may be attached to the same [LedgerTransaction].
|
||||||
|
@ -4,7 +4,6 @@ import net.corda.core.contracts.ContractState
|
|||||||
import net.corda.core.contracts.FungibleAsset
|
import net.corda.core.contracts.FungibleAsset
|
||||||
import net.corda.core.contracts.OwnableState
|
import net.corda.core.contracts.OwnableState
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
import net.corda.core.contracts.UniqueIdentifier
|
||||||
import net.corda.core.crypto.toBase58String
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
|
125
core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
Normal file
125
core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package net.corda.core.schemas
|
||||||
|
|
||||||
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.node.ServiceEntry
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.utilities.toBase58String
|
||||||
|
import java.io.Serializable
|
||||||
|
import javax.persistence.*
|
||||||
|
|
||||||
|
object NodeInfoSchema
|
||||||
|
|
||||||
|
object NodeInfoSchemaV1 : MappedSchema(
|
||||||
|
schemaFamily = NodeInfoSchema.javaClass,
|
||||||
|
version = 1,
|
||||||
|
mappedTypes = listOf(PersistentNodeInfo::class.java, DBPartyAndCertificate::class.java, DBHostAndPort::class.java)
|
||||||
|
) {
|
||||||
|
@Entity
|
||||||
|
@Table(name = "node_infos")
|
||||||
|
class PersistentNodeInfo(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
@Column(name = "node_info_id")
|
||||||
|
var id: Int,
|
||||||
|
|
||||||
|
@Column(name = "addresses")
|
||||||
|
@OneToMany(cascade = arrayOf(CascadeType.ALL), orphanRemoval = true)
|
||||||
|
val addresses: List<NodeInfoSchemaV1.DBHostAndPort>,
|
||||||
|
|
||||||
|
@Column(name = "legal_identities_certs")
|
||||||
|
@ManyToMany(cascade = arrayOf(CascadeType.ALL))
|
||||||
|
@JoinTable(name = "link_nodeinfo_party",
|
||||||
|
joinColumns = arrayOf(JoinColumn(name="node_info_id")),
|
||||||
|
inverseJoinColumns = arrayOf(JoinColumn(name="party_name")))
|
||||||
|
val legalIdentitiesAndCerts: Set<DBPartyAndCertificate>,
|
||||||
|
|
||||||
|
@Column(name = "platform_version")
|
||||||
|
val platformVersion: Int,
|
||||||
|
|
||||||
|
@Column(name = "advertised_services")
|
||||||
|
@ElementCollection
|
||||||
|
var advertisedServices: List<DBServiceEntry> = emptyList(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* serial is an increasing value which represents the version of [NodeInfo].
|
||||||
|
* Not expected to be sequential, but later versions of the registration must have higher values
|
||||||
|
* Similar to the serial number on DNS records.
|
||||||
|
*/
|
||||||
|
@Column(name = "serial")
|
||||||
|
val serial: Long
|
||||||
|
) {
|
||||||
|
fun toNodeInfo(): NodeInfo {
|
||||||
|
return NodeInfo(
|
||||||
|
this.addresses.map { it.toHostAndPort() },
|
||||||
|
this.legalIdentitiesAndCerts.filter { it.isMain }.single().toLegalIdentityAndCert(), // TODO Workaround, it will be changed after PR with services removal.
|
||||||
|
this.legalIdentitiesAndCerts.filter { !it.isMain }.map { it.toLegalIdentityAndCert() }.toSet(),
|
||||||
|
this.platformVersion,
|
||||||
|
this.advertisedServices.map {
|
||||||
|
it.serviceEntry?.deserialize<ServiceEntry>() ?: throw IllegalStateException("Service entry shouldn't be null")
|
||||||
|
},
|
||||||
|
this.serial
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
data class PKHostAndPort(
|
||||||
|
val host: String? = null,
|
||||||
|
val port: Int? = null
|
||||||
|
) : Serializable
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class DBHostAndPort(
|
||||||
|
@EmbeddedId
|
||||||
|
private val pk: PKHostAndPort
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun fromHostAndPort(hostAndPort: NetworkHostAndPort) = DBHostAndPort(
|
||||||
|
PKHostAndPort(hostAndPort.host, hostAndPort.port)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fun toHostAndPort(): NetworkHostAndPort {
|
||||||
|
return NetworkHostAndPort(this.pk.host!!, this.pk.port!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable // TODO To be removed with services.
|
||||||
|
data class DBServiceEntry(
|
||||||
|
@Column(length = 65535)
|
||||||
|
val serviceEntry: ByteArray? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PartyAndCertificate entity (to be replaced by referencing final Identity Schema).
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "node_info_party_cert")
|
||||||
|
data class DBPartyAndCertificate(
|
||||||
|
@Id
|
||||||
|
@Column(name = "owning_key", length = 65535, nullable = false)
|
||||||
|
val owningKey: String,
|
||||||
|
|
||||||
|
//@Id // TODO Do we assume that names are unique? Note: We can't have it as Id, because our toString on X500 is inconsistent.
|
||||||
|
@Column(name = "party_name", nullable = false)
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@Column(name = "party_cert_binary")
|
||||||
|
@Lob
|
||||||
|
val partyCertBinary: ByteArray,
|
||||||
|
|
||||||
|
val isMain: Boolean,
|
||||||
|
|
||||||
|
@ManyToMany(mappedBy = "legalIdentitiesAndCerts", cascade = arrayOf(CascadeType.ALL)) // ManyToMany because of distributed services.
|
||||||
|
private val persistentNodeInfos: Set<PersistentNodeInfo> = emptySet()
|
||||||
|
) {
|
||||||
|
constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false)
|
||||||
|
: this(partyAndCert.party.owningKey.toBase58String(), partyAndCert.party.name.toString(), partyAndCert.serialize().bytes, isMain)
|
||||||
|
|
||||||
|
fun toLegalIdentityAndCert(): PartyAndCertificate {
|
||||||
|
return partyCertBinary.deserialize<PartyAndCertificate>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,6 @@ package net.corda.core.serialization
|
|||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.internal.WriteOnceProperty
|
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.ByteSequence
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.sequence
|
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
|
* An abstraction for serializing and deserializing objects, with support for versioning of the wire format via
|
||||||
* a header / prefix in the bytes.
|
* 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.
|
* 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 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.
|
* @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.
|
* 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 obj The object to be serialized.
|
||||||
* @param context A context that configures various parameters to serialization, including the serialization format version.
|
* @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
|
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.
|
* 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.
|
* 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)
|
return serializationFactory.deserialize(this, T::class.java, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
|
* 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)
|
return serializationFactory.deserialize(this, T::class.java, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience extension method for deserializing a ByteArray, utilising the defaults.
|
* 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.
|
* 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)
|
return serializationFactory.serialize(this, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.core.serialization
|
package net.corda.core.serialization
|
||||||
|
|
||||||
interface SerializationCustomization {
|
interface SerializationCustomization {
|
||||||
fun addToWhitelist(type: Class<*>)
|
fun addToWhitelist(vararg types: Class<*>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ abstract class BaseTransaction : NamedByHash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkNotarySetIfInputsPresent() {
|
private fun checkNotarySetIfInputsPresent() {
|
||||||
if (notary == null) {
|
if (inputs.isNotEmpty()) {
|
||||||
check(inputs.isEmpty()) { "The notary must be specified explicitly for any transaction that has inputs" }
|
check(notary != null) { "The notary must be specified explicitly for any transaction that has inputs" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import java.util.function.Predicate
|
|||||||
* - Downloading and locally storing all the dependencies of the transaction.
|
* - Downloading and locally storing all the dependencies of the transaction.
|
||||||
* - Resolving the input states and loading them into memory.
|
* - 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
|
* - 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.
|
* - Deserialising the output states.
|
||||||
*
|
*
|
||||||
* All the above refer to inputs using a (txhash, output index) pair.
|
* 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 inputs: List<StateAndRef<ContractState>>,
|
||||||
override val outputs: List<TransactionState<ContractState>>,
|
override val outputs: List<TransactionState<ContractState>>,
|
||||||
/** Arbitrary data passed to the program of each input state. */
|
/** 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. */
|
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||||
val attachments: List<Attachment>,
|
val attachments: List<Attachment>,
|
||||||
/** The hash of the original serialised WireTransaction. */
|
/** The hash of the original serialised WireTransaction. */
|
||||||
|
@ -4,7 +4,7 @@ import net.corda.core.contracts.*
|
|||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
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 net.corda.core.serialization.serialize
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.function.Predicate
|
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 {
|
fun <T : Any> serializedHash(x: T, nonce: SecureHash): SecureHash {
|
||||||
return if (x !is PrivacySalt) // PrivacySalt is not required to have an accompanied nonce.
|
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
|
else
|
||||||
serializedHash(x)
|
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). */
|
/** The nonce is computed as Hash(privacySalt || index). */
|
||||||
fun computeNonce(privacySalt: PrivacySalt, index: Int) = (privacySalt.bytes + ByteBuffer.allocate(4).putInt(index).array()).sha256()
|
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.
|
* 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 filteredLeaves Leaves included in a filtered transaction.
|
||||||
* @param partialMerkleTree Merkle branch needed to verify filteredLeaves.
|
* @param partialMerkleTree Merkle branch needed to verify filteredLeaves.
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class FilteredTransaction private constructor(
|
class FilteredTransaction private constructor(
|
||||||
val rootHash: SecureHash,
|
val id: SecureHash,
|
||||||
val filteredLeaves: FilteredLeaves,
|
val filteredLeaves: FilteredLeaves,
|
||||||
val partialMerkleTree: PartialMerkleTree
|
val partialMerkleTree: PartialMerkleTree
|
||||||
) {
|
) {
|
||||||
@ -159,21 +159,84 @@ class FilteredTransaction private constructor(
|
|||||||
fun buildMerkleTransaction(wtx: WireTransaction,
|
fun buildMerkleTransaction(wtx: WireTransaction,
|
||||||
filtering: Predicate<Any>
|
filtering: Predicate<Any>
|
||||||
): FilteredTransaction {
|
): FilteredTransaction {
|
||||||
val filteredLeaves = wtx.filterWithFun(filtering)
|
val filteredLeaves = filterWithFun(wtx, filtering)
|
||||||
val merkleTree = wtx.merkleTree
|
val merkleTree = wtx.merkleTree
|
||||||
val pmt = PartialMerkleTree.build(merkleTree, filteredLeaves.availableComponentHashes)
|
val pmt = PartialMerkleTree.build(merkleTree, filteredLeaves.availableComponentHashes)
|
||||||
return FilteredTransaction(merkleTree.hash, filteredLeaves, pmt)
|
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)
|
@Throws(MerkleTreeException::class)
|
||||||
fun verify(): Boolean {
|
fun verify(): Boolean {
|
||||||
val hashes: List<SecureHash> = filteredLeaves.availableComponentHashes
|
val hashes: List<SecureHash> = filteredLeaves.availableComponentHashes
|
||||||
if (hashes.isEmpty())
|
if (hashes.isEmpty())
|
||||||
throw MerkleTreeException("Transaction without included leaves.")
|
throw MerkleTreeException("Transaction without included leaves.")
|
||||||
return partialMerkleTree.verify(rootHash, hashes)
|
return partialMerkleTree.verify(id, hashes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ package net.corda.core.transactions
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.TransactionSignature
|
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.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.security.PublicKey
|
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
|
* old and new notaries. Output states can be computed by applying the notary modification to corresponding inputs
|
||||||
* on the fly.
|
* on the fly.
|
||||||
*/
|
*/
|
||||||
|
@CordaSerializable
|
||||||
data class NotaryChangeWireTransaction(
|
data class NotaryChangeWireTransaction(
|
||||||
override val inputs: List<StateRef>,
|
override val inputs: List<StateRef>,
|
||||||
override val notary: Party,
|
override val notary: Party,
|
||||||
|
@ -14,6 +14,7 @@ import java.security.KeyPair
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.function.Predicate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SignedTransaction wraps a serialized WireTransaction. It contains one or more signatures, each one for
|
* SignedTransaction wraps a serialized WireTransaction. It contains one or more signatures, each one for
|
||||||
@ -29,6 +30,7 @@ import java.util.*
|
|||||||
* sign.
|
* sign.
|
||||||
*/
|
*/
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
|
@CordaSerializable
|
||||||
data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||||
override val sigs: List<TransactionSignature>
|
override val sigs: List<TransactionSignature>
|
||||||
) : TransactionWithSignatures {
|
) : TransactionWithSignatures {
|
||||||
@ -50,12 +52,18 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
/** The id of the contained [WireTransaction]. */
|
/** The id of the contained [WireTransaction]. */
|
||||||
override val id: SecureHash get() = transaction.id
|
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
|
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
|
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 */
|
/** Helper to access the inputs of the contained transaction */
|
||||||
val inputs: List<StateRef> get() = transaction.inputs
|
val inputs: List<StateRef> get() = transaction.inputs
|
||||||
/** Helper to access the notary of the contained transaction */
|
/** Helper to access the notary of the contained transaction */
|
||||||
|
@ -33,8 +33,9 @@ data class WireTransaction(
|
|||||||
) : CoreTransaction(), TraversableTransaction {
|
) : CoreTransaction(), TraversableTransaction {
|
||||||
init {
|
init {
|
||||||
checkBaseInvariants()
|
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" }
|
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. */
|
/** 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.
|
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
|
||||||
val authenticatedArgs = commands.map {
|
val authenticatedArgs = commands.map {
|
||||||
val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) }
|
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.
|
// Open attachments specified in this transaction. If we haven't downloaded them, we fail.
|
||||||
val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
|
val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
|
||||||
@ -105,69 +106,6 @@ data class WireTransaction(
|
|||||||
*/
|
*/
|
||||||
val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(availableComponentHashes) }
|
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.
|
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
|
||||||
*
|
*
|
||||||
|
253
core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt
Normal file
253
core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
package net.corda.core.utilities
|
||||||
|
|
||||||
|
val countryCodes = hashSetOf(
|
||||||
|
"AF",
|
||||||
|
"AX",
|
||||||
|
"AL",
|
||||||
|
"DZ",
|
||||||
|
"AS",
|
||||||
|
"AD",
|
||||||
|
"AO",
|
||||||
|
"AI",
|
||||||
|
"AQ",
|
||||||
|
"AG",
|
||||||
|
"AR",
|
||||||
|
"AM",
|
||||||
|
"AW",
|
||||||
|
"AU",
|
||||||
|
"AT",
|
||||||
|
"AZ",
|
||||||
|
"BS",
|
||||||
|
"BH",
|
||||||
|
"BD",
|
||||||
|
"BB",
|
||||||
|
"BY",
|
||||||
|
"BE",
|
||||||
|
"BZ",
|
||||||
|
"BJ",
|
||||||
|
"BM",
|
||||||
|
"BT",
|
||||||
|
"BO",
|
||||||
|
"BQ",
|
||||||
|
"BA",
|
||||||
|
"BW",
|
||||||
|
"BV",
|
||||||
|
"BR",
|
||||||
|
"IO",
|
||||||
|
"BN",
|
||||||
|
"BG",
|
||||||
|
"BF",
|
||||||
|
"BI",
|
||||||
|
"KH",
|
||||||
|
"CM",
|
||||||
|
"CA",
|
||||||
|
"CV",
|
||||||
|
"KY",
|
||||||
|
"CF",
|
||||||
|
"TD",
|
||||||
|
"CL",
|
||||||
|
"CN",
|
||||||
|
"CX",
|
||||||
|
"CC",
|
||||||
|
"CO",
|
||||||
|
"KM",
|
||||||
|
"CG",
|
||||||
|
"CD",
|
||||||
|
"CK",
|
||||||
|
"CR",
|
||||||
|
"CI",
|
||||||
|
"HR",
|
||||||
|
"CU",
|
||||||
|
"CW",
|
||||||
|
"CY",
|
||||||
|
"CZ",
|
||||||
|
"DK",
|
||||||
|
"DJ",
|
||||||
|
"DM",
|
||||||
|
"DO",
|
||||||
|
"EC",
|
||||||
|
"EG",
|
||||||
|
"SV",
|
||||||
|
"GQ",
|
||||||
|
"ER",
|
||||||
|
"EE",
|
||||||
|
"ET",
|
||||||
|
"FK",
|
||||||
|
"FO",
|
||||||
|
"FJ",
|
||||||
|
"FI",
|
||||||
|
"FR",
|
||||||
|
"GF",
|
||||||
|
"PF",
|
||||||
|
"TF",
|
||||||
|
"GA",
|
||||||
|
"GM",
|
||||||
|
"GE",
|
||||||
|
"DE",
|
||||||
|
"GH",
|
||||||
|
"GI",
|
||||||
|
"GR",
|
||||||
|
"GL",
|
||||||
|
"GD",
|
||||||
|
"GP",
|
||||||
|
"GU",
|
||||||
|
"GT",
|
||||||
|
"GG",
|
||||||
|
"GN",
|
||||||
|
"GW",
|
||||||
|
"GY",
|
||||||
|
"HT",
|
||||||
|
"HM",
|
||||||
|
"VA",
|
||||||
|
"HN",
|
||||||
|
"HK",
|
||||||
|
"HU",
|
||||||
|
"IS",
|
||||||
|
"IN",
|
||||||
|
"ID",
|
||||||
|
"IR",
|
||||||
|
"IQ",
|
||||||
|
"IE",
|
||||||
|
"IM",
|
||||||
|
"IL",
|
||||||
|
"IT",
|
||||||
|
"JM",
|
||||||
|
"JP",
|
||||||
|
"JE",
|
||||||
|
"JO",
|
||||||
|
"KZ",
|
||||||
|
"KE",
|
||||||
|
"KI",
|
||||||
|
"KP",
|
||||||
|
"KR",
|
||||||
|
"KW",
|
||||||
|
"KG",
|
||||||
|
"LA",
|
||||||
|
"LV",
|
||||||
|
"LB",
|
||||||
|
"LS",
|
||||||
|
"LR",
|
||||||
|
"LY",
|
||||||
|
"LI",
|
||||||
|
"LT",
|
||||||
|
"LU",
|
||||||
|
"MO",
|
||||||
|
"MK",
|
||||||
|
"MG",
|
||||||
|
"MW",
|
||||||
|
"MY",
|
||||||
|
"MV",
|
||||||
|
"ML",
|
||||||
|
"MT",
|
||||||
|
"MH",
|
||||||
|
"MQ",
|
||||||
|
"MR",
|
||||||
|
"MU",
|
||||||
|
"YT",
|
||||||
|
"MX",
|
||||||
|
"FM",
|
||||||
|
"MD",
|
||||||
|
"MC",
|
||||||
|
"MN",
|
||||||
|
"ME",
|
||||||
|
"MS",
|
||||||
|
"MA",
|
||||||
|
"MZ",
|
||||||
|
"MM",
|
||||||
|
"NA",
|
||||||
|
"NR",
|
||||||
|
"NP",
|
||||||
|
"NL",
|
||||||
|
"NC",
|
||||||
|
"NZ",
|
||||||
|
"NI",
|
||||||
|
"NE",
|
||||||
|
"NG",
|
||||||
|
"NU",
|
||||||
|
"NF",
|
||||||
|
"MP",
|
||||||
|
"NO",
|
||||||
|
"OM",
|
||||||
|
"PK",
|
||||||
|
"PW",
|
||||||
|
"PS",
|
||||||
|
"PA",
|
||||||
|
"PG",
|
||||||
|
"PY",
|
||||||
|
"PE",
|
||||||
|
"PH",
|
||||||
|
"PN",
|
||||||
|
"PL",
|
||||||
|
"PT",
|
||||||
|
"PR",
|
||||||
|
"QA",
|
||||||
|
"RE",
|
||||||
|
"RO",
|
||||||
|
"RU",
|
||||||
|
"RW",
|
||||||
|
"BL",
|
||||||
|
"SH",
|
||||||
|
"KN",
|
||||||
|
"LC",
|
||||||
|
"MF",
|
||||||
|
"PM",
|
||||||
|
"VC",
|
||||||
|
"WS",
|
||||||
|
"SM",
|
||||||
|
"ST",
|
||||||
|
"SA",
|
||||||
|
"SN",
|
||||||
|
"RS",
|
||||||
|
"SC",
|
||||||
|
"SL",
|
||||||
|
"SG",
|
||||||
|
"SX",
|
||||||
|
"SK",
|
||||||
|
"SI",
|
||||||
|
"SB",
|
||||||
|
"SO",
|
||||||
|
"ZA",
|
||||||
|
"GS",
|
||||||
|
"SS",
|
||||||
|
"ES",
|
||||||
|
"LK",
|
||||||
|
"SD",
|
||||||
|
"SR",
|
||||||
|
"SJ",
|
||||||
|
"SZ",
|
||||||
|
"SE",
|
||||||
|
"CH",
|
||||||
|
"SY",
|
||||||
|
"TW",
|
||||||
|
"TJ",
|
||||||
|
"TZ",
|
||||||
|
"TH",
|
||||||
|
"TL",
|
||||||
|
"TG",
|
||||||
|
"TK",
|
||||||
|
"TO",
|
||||||
|
"TT",
|
||||||
|
"TN",
|
||||||
|
"TR",
|
||||||
|
"TM",
|
||||||
|
"TC",
|
||||||
|
"TV",
|
||||||
|
"UG",
|
||||||
|
"UA",
|
||||||
|
"AE",
|
||||||
|
"GB",
|
||||||
|
"US",
|
||||||
|
"UM",
|
||||||
|
"UY",
|
||||||
|
"UZ",
|
||||||
|
"VU",
|
||||||
|
"VE",
|
||||||
|
"VN",
|
||||||
|
"VG",
|
||||||
|
"VI",
|
||||||
|
"WF",
|
||||||
|
"EH",
|
||||||
|
"YE",
|
||||||
|
"ZM",
|
||||||
|
"ZW"
|
||||||
|
)
|
@ -1,7 +1,9 @@
|
|||||||
@file:JvmName("EncodingUtils")
|
@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.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
package net.corda.core.utilities
|
package net.corda.core.utilities
|
||||||
|
|
||||||
import net.corda.core.crypto.commonName
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||||
import java.lang.Character.UnicodeScript.*
|
import java.lang.Character.UnicodeScript.*
|
||||||
import java.text.Normalizer
|
import java.text.Normalizer
|
||||||
import java.util.regex.Pattern
|
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.
|
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
||||||
*/
|
*/
|
||||||
fun validateLegalName(normalizedLegalName: String) {
|
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,
|
* 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.
|
* and normalize the string according to NFKC normalization form.
|
||||||
@ -41,7 +34,62 @@ fun normaliseLegalName(legalName: String): String {
|
|||||||
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
|
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val legalNameRules: List<Rule<String>> = listOf(
|
val WHITESPACE = "\\s++".toRegex()
|
||||||
|
|
||||||
|
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 sealed class Rule<in T> {
|
||||||
|
companion object {
|
||||||
|
val legalNameRules: List<Rule<String>> = listOf(
|
||||||
UnicodeNormalizationRule(),
|
UnicodeNormalizationRule(),
|
||||||
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
||||||
WordRule("node", "server"),
|
WordRule("node", "server"),
|
||||||
@ -52,14 +100,17 @@ private val legalNameRules: List<Rule<String>> = listOf(
|
|||||||
X500NameRule(),
|
X500NameRule(),
|
||||||
MustHaveAtLeastTwoLettersRule()
|
MustHaveAtLeastTwoLettersRule()
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private class UnicodeNormalizationRule : Rule<String> {
|
abstract fun validate(legalName: T)
|
||||||
|
|
||||||
|
private class UnicodeNormalizationRule : Rule<String>() {
|
||||||
override fun validate(legalName: 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." }
|
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 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 val pattern = supportScripts.map { "\\p{Is$it}" }.joinToString(separator = "", prefix = "[", postfix = "]*").let { Pattern.compile(it) }
|
||||||
|
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
@ -74,7 +125,7 @@ private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeScript) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CharacterRule(vararg val bannedChars: Char) : Rule<String> {
|
private class CharacterRule(vararg val bannedChars: Char) : Rule<String>() {
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
bannedChars.forEach {
|
bannedChars.forEach {
|
||||||
require(!legalName.contains(it, true)) { "Character not allowed in legal names: $it" }
|
require(!legalName.contains(it, true)) { "Character not allowed in legal names: $it" }
|
||||||
@ -82,7 +133,7 @@ private class CharacterRule(vararg val bannedChars: Char) : Rule<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WordRule(vararg val bannedWords: String) : Rule<String> {
|
private class WordRule(vararg val bannedWords: String) : Rule<String>() {
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
bannedWords.forEach {
|
bannedWords.forEach {
|
||||||
require(!legalName.contains(it, ignoreCase = true)) { "Word not allowed in legal names: $it" }
|
require(!legalName.contains(it, ignoreCase = true)) { "Word not allowed in legal names: $it" }
|
||||||
@ -90,33 +141,30 @@ private class WordRule(vararg val bannedWords: String) : Rule<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LengthRule(val maxLength: Int) : Rule<String> {
|
private class LengthRule(val maxLength: Int) : Rule<String>() {
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
|
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CapitalLetterRule : Rule<String> {
|
private class CapitalLetterRule : Rule<String>() {
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
val capitalizedLegalName = legalName.capitalize()
|
val capitalizedLegalName = legalName.capitalize()
|
||||||
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
|
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class X500NameRule : Rule<String> {
|
private class X500NameRule : Rule<String>() {
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
|
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
|
||||||
X500Principal("CN=$legalName")
|
X500Principal("CN=$legalName")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MustHaveAtLeastTwoLettersRule : Rule<String> {
|
private class MustHaveAtLeastTwoLettersRule : Rule<String>() {
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
// Try to exclude names like "/", "£", "X" etc.
|
// 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" }
|
require(legalName.count { it.isLetter() } >= 2) { "Illegal input legal name '$legalName'. Legal name must have at least two letters" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface Rule<in T> {
|
|
||||||
fun validate(legalName: T)
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
@file:JvmName("X500NameUtils")
|
||||||
|
|
||||||
|
package net.corda.core.utilities
|
||||||
|
|
||||||
|
import net.corda.core.internal.toX509CertHolder
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import org.bouncycastle.asn1.x500.X500NameBuilder
|
||||||
|
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
|
||||||
|
val X500Name.commonName: String? get() = getRDNValueString(BCStyle.CN)
|
||||||
|
val X500Name.organisationUnit: String? get() = getRDNValueString(BCStyle.OU)
|
||||||
|
val X500Name.state: String? get() = getRDNValueString(BCStyle.ST)
|
||||||
|
val X500Name.organisation: String get() = getRDNValueString(BCStyle.O) ?: throw IllegalArgumentException("Malformed X500 name, organisation attribute (O) cannot be empty.")
|
||||||
|
val X500Name.locality: String get() = getRDNValueString(BCStyle.L) ?: throw IllegalArgumentException("Malformed X500 name, locality attribute (L) cannot be empty.")
|
||||||
|
val X500Name.country: String get() = getRDNValueString(BCStyle.C) ?: throw IllegalArgumentException("Malformed X500 name, country attribute (C) cannot be empty.")
|
||||||
|
|
||||||
|
private fun X500Name.getRDNValueString(identifier: ASN1ObjectIdentifier): String? = getRDNs(identifier).firstOrNull()?.first?.value?.toString()
|
||||||
|
|
||||||
|
val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
|
||||||
|
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a distinguished name from the provided X500 .
|
||||||
|
*
|
||||||
|
* @param O organisation name.
|
||||||
|
* @param L locality.
|
||||||
|
* @param C county.
|
||||||
|
* @param CN common name.
|
||||||
|
* @param OU organisation unit.
|
||||||
|
* @param ST state.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun getX500Name(O: String, L: String, C: String, CN: String? = null, OU: String? = null, ST: String? = null): X500Name {
|
||||||
|
return X500NameBuilder(BCStyle.INSTANCE).apply {
|
||||||
|
addRDN(BCStyle.C, C)
|
||||||
|
ST?.let { addRDN(BCStyle.ST, it) }
|
||||||
|
addRDN(BCStyle.L, L)
|
||||||
|
addRDN(BCStyle.O, O)
|
||||||
|
OU?.let { addRDN(BCStyle.OU, it) }
|
||||||
|
CN?.let { addRDN(BCStyle.CN, it) }
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun X500Name.withCommonName(commonName: String?): X500Name {
|
||||||
|
return getX500Name(organisation, locality, country, commonName, organisationUnit, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun X500Name.toWellFormattedName(): X500Name {
|
||||||
|
validateX500Name(this)
|
||||||
|
return getX500Name(organisation, locality, country, commonName, organisationUnit, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
@ -1,15 +1,18 @@
|
|||||||
package net.corda.core.flows;
|
package net.corda.core.flows;
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable;
|
import co.paralleluniverse.fibers.Suspendable;
|
||||||
|
import com.google.common.primitives.Primitives;
|
||||||
import net.corda.core.identity.Party;
|
import net.corda.core.identity.Party;
|
||||||
import net.corda.testing.node.MockNetwork;
|
import net.corda.testing.node.MockNetwork;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class FlowsInJavaTest {
|
public class FlowsInJavaTest {
|
||||||
|
|
||||||
@ -18,11 +21,13 @@ public class FlowsInJavaTest {
|
|||||||
private MockNetwork.MockNode node2;
|
private MockNetwork.MockNode node2;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() throws Exception {
|
||||||
MockNetwork.BasketOfNodes someNodes = mockNet.createSomeNodes(2);
|
MockNetwork.BasketOfNodes someNodes = mockNet.createSomeNodes(2);
|
||||||
node1 = someNodes.getPartyNodes().get(0);
|
node1 = someNodes.getPartyNodes().get(0);
|
||||||
node2 = someNodes.getPartyNodes().get(1);
|
node2 = someNodes.getPartyNodes().get(1);
|
||||||
mockNet.runNetwork();
|
mockNet.runNetwork();
|
||||||
|
// Ensure registration was successful
|
||||||
|
node1.getNodeReadyFuture().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -38,6 +43,30 @@ public class FlowsInJavaTest {
|
|||||||
assertThat(result.get()).isEqualTo("Hello");
|
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
|
@InitiatingFlow
|
||||||
private static class SendInUnwrapFlow extends FlowLogic<String> {
|
private static class SendInUnwrapFlow extends FlowLogic<String> {
|
||||||
private final Party otherParty;
|
private final Party otherParty;
|
||||||
@ -71,4 +100,21 @@ public class FlowsInJavaTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InitiatingFlow
|
||||||
|
private static class PrimitiveReceiveFlow extends FlowLogic<Void> {
|
||||||
|
private final Party otherParty;
|
||||||
|
private final Class<?> receiveType;
|
||||||
|
|
||||||
|
private PrimitiveReceiveFlow(Party otherParty, Class<?> receiveType) {
|
||||||
|
this.otherParty = otherParty;
|
||||||
|
this.receiveType = receiveType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
@Override
|
||||||
|
public Void call() throws FlowException {
|
||||||
|
receive(Primitives.unwrap(receiveType), otherParty);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ class AttachmentTest {
|
|||||||
val attachment = object : Attachment {
|
val attachment = object : Attachment {
|
||||||
override val id get() = throw UnsupportedOperationException()
|
override val id get() = throw UnsupportedOperationException()
|
||||||
override fun open() = inputStream
|
override fun open() = inputStream
|
||||||
|
override val signers get() = throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
attachment.openAsJAR()
|
attachment.openAsJAR()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.core.contracts
|
package net.corda.core.contracts
|
||||||
|
|
||||||
import net.corda.core.crypto.*
|
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.identity.Party
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -96,7 +96,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
|||||||
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_NOTARY)
|
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_NOTARY)
|
||||||
val inputs = emptyList<StateAndRef<*>>()
|
val inputs = emptyList<StateAndRef<*>>()
|
||||||
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
|
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 attachments = emptyList<Attachment>()
|
||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
@ -137,7 +137,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
|||||||
val outState = inState.copy(notary = ALICE)
|
val outState = inState.copy(notary = ALICE)
|
||||||
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
|
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
|
||||||
val outputs = listOf(outState)
|
val outputs = listOf(outState)
|
||||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
val commands = emptyList<CommandWithParties<CommandData>>()
|
||||||
val attachments = emptyList<Attachment>()
|
val attachments = emptyList<Attachment>()
|
||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package net.corda.core.crypto
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.crypto.composite.CompositeKey
|
import net.corda.core.crypto.CompositeKey.NodeAndWeight
|
||||||
import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight
|
|
||||||
import net.corda.core.crypto.composite.CompositeSignature
|
|
||||||
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
|
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
|
||||||
import net.corda.core.internal.declaredField
|
import net.corda.core.internal.declaredField
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
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.node.utilities.*
|
||||||
import net.corda.testing.TestDependencyInjectionBase
|
import net.corda.testing.TestDependencyInjectionBase
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import net.corda.testing.kryoSpecific
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
@ -88,7 +89,7 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
|||||||
val aliceAndBobOrCharlie = CompositeKey.Builder().addKeys(aliceAndBob, charliePublicKey).build(threshold = 1)
|
val aliceAndBobOrCharlie = CompositeKey.Builder().addKeys(aliceAndBob, charliePublicKey).build(threshold = 1)
|
||||||
|
|
||||||
val encoded = aliceAndBobOrCharlie.toBase58String()
|
val encoded = aliceAndBobOrCharlie.toBase58String()
|
||||||
val decoded = parsePublicKeyBase58(encoded)
|
val decoded = net.corda.core.utilities.parsePublicKeyBase58(encoded)
|
||||||
|
|
||||||
assertEquals(decoded, aliceAndBobOrCharlie)
|
assertEquals(decoded, aliceAndBobOrCharlie)
|
||||||
}
|
}
|
||||||
@ -216,7 +217,7 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test()
|
@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 key1 = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey).build() as CompositeKey
|
||||||
val key2 = CompositeKey.Builder().addKeys(alicePublicKey, key1).build() as CompositeKey
|
val key2 = CompositeKey.Builder().addKeys(alicePublicKey, key1).build() as CompositeKey
|
||||||
val key3 = CompositeKey.Builder().addKeys(alicePublicKey, key2).build() as CompositeKey
|
val key3 = CompositeKey.Builder().addKeys(alicePublicKey, key2).build() as CompositeKey
|
||||||
@ -330,10 +331,10 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
|||||||
|
|
||||||
// Create self sign CA.
|
// Create self sign CA.
|
||||||
val caKeyPair = Crypto.generateKeyPair()
|
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.
|
// 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.
|
// Store certificate to keystore.
|
||||||
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
|
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.corda.core.crypto
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.internal.toTypedArray
|
import net.corda.core.internal.toTypedArray
|
||||||
|
import net.corda.core.utilities.cert
|
||||||
|
import net.corda.core.utilities.getX500Name
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.asn1.x509.GeneralName
|
import org.bouncycastle.asn1.x509.GeneralName
|
||||||
@ -18,13 +20,13 @@ class X509NameConstraintsTest {
|
|||||||
|
|
||||||
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
|
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
|
||||||
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
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 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 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 keyPass = "password"
|
||||||
val trustStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
val trustStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||||
|
@ -56,6 +56,11 @@ class AttachmentTests {
|
|||||||
val nodes = mockNet.createSomeNodes(2)
|
val nodes = mockNet.createSomeNodes(2)
|
||||||
val n0 = nodes.partyNodes[0]
|
val n0 = nodes.partyNodes[0]
|
||||||
val n1 = nodes.partyNodes[1]
|
val n1 = nodes.partyNodes[1]
|
||||||
|
|
||||||
|
// Ensure that registration was successful before progressing any further
|
||||||
|
mockNet.runNetwork()
|
||||||
|
n0.ensureRegistered()
|
||||||
|
|
||||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
|
|
||||||
@ -89,6 +94,11 @@ class AttachmentTests {
|
|||||||
val nodes = mockNet.createSomeNodes(2)
|
val nodes = mockNet.createSomeNodes(2)
|
||||||
val n0 = nodes.partyNodes[0]
|
val n0 = nodes.partyNodes[0]
|
||||||
val n1 = nodes.partyNodes[1]
|
val n1 = nodes.partyNodes[1]
|
||||||
|
|
||||||
|
// Ensure that registration was successful before progressing any further
|
||||||
|
mockNet.runNetwork()
|
||||||
|
n0.ensureRegistered()
|
||||||
|
|
||||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
|
|
||||||
@ -119,6 +129,10 @@ class AttachmentTests {
|
|||||||
}, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type)))
|
}, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type)))
|
||||||
val n1 = mockNet.createNode(n0.network.myAddress)
|
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)
|
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ class CollectSignaturesFlowTests {
|
|||||||
c = nodes.partyNodes[2]
|
c = nodes.partyNodes[2]
|
||||||
notary = nodes.notaryNode.info.notaryIdentity
|
notary = nodes.notaryNode.info.notaryIdentity
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
a.ensureRegistered()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -140,9 +141,13 @@ class CollectSignaturesFlowTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `successfully collects two signatures`() {
|
fun `successfully collects two signatures`() {
|
||||||
val bConfidentialIdentity = b.services.keyManagementService.freshKeyAndCert(b.info.legalIdentityAndCert, false)
|
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
|
// Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity
|
||||||
a.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity)
|
a.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity)
|
||||||
|
}
|
||||||
registerFlowOnAllNodes(TestFlowTwo.Responder::class)
|
registerFlowOnAllNodes(TestFlowTwo.Responder::class)
|
||||||
val magicNumber = 1337
|
val magicNumber = 1337
|
||||||
val parties = listOf(a.info.legalIdentity, bConfidentialIdentity.party, c.info.legalIdentity)
|
val parties = listOf(a.info.legalIdentity, bConfidentialIdentity.party, c.info.legalIdentity)
|
||||||
|
@ -46,13 +46,23 @@ class ContractUpgradeFlowTest {
|
|||||||
val nodes = mockNet.createSomeNodes(notaryKeyPair = null) // prevent generation of notary override
|
val nodes = mockNet.createSomeNodes(notaryKeyPair = null) // prevent generation of notary override
|
||||||
a = nodes.partyNodes[0]
|
a = nodes.partyNodes[0]
|
||||||
b = nodes.partyNodes[1]
|
b = nodes.partyNodes[1]
|
||||||
|
|
||||||
|
// Process registration
|
||||||
|
mockNet.runNetwork()
|
||||||
|
a.ensureRegistered()
|
||||||
|
|
||||||
notary = nodes.notaryNode.info.notaryIdentity
|
notary = nodes.notaryNode.info.notaryIdentity
|
||||||
|
|
||||||
val nodeIdentity = nodes.notaryNode.info.legalIdentitiesAndCerts.single { it.party == nodes.notaryNode.info.notaryIdentity }
|
val nodeIdentity = nodes.notaryNode.info.legalIdentitiesAndCerts.single { it.party == nodes.notaryNode.info.notaryIdentity }
|
||||||
|
a.database.transaction {
|
||||||
a.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
a.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||||
|
}
|
||||||
|
b.database.transaction {
|
||||||
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
mockNet.stopNodes()
|
mockNet.stopNodes()
|
||||||
@ -74,15 +84,24 @@ class ContractUpgradeFlowTest {
|
|||||||
requireNotNull(btx)
|
requireNotNull(btx)
|
||||||
|
|
||||||
// The request is expected to be rejected because party B hasn't authorised the upgrade yet.
|
// 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()
|
mockNet.runNetwork()
|
||||||
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
||||||
|
|
||||||
// Party B authorise the contract state upgrade.
|
// Party B authorise the contract state upgrade, and immediately deauthorise the same.
|
||||||
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
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.
|
// 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()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
val result = resultFuture.getOrThrow()
|
val result = resultFuture.getOrThrow()
|
||||||
@ -128,7 +147,10 @@ class ContractUpgradeFlowTest {
|
|||||||
|
|
||||||
val user = rpcTestUser.copy(permissions = setOf(
|
val user = rpcTestUser.copy(permissions = setOf(
|
||||||
startFlowPermission<FinalityInvoker>(),
|
startFlowPermission<FinalityInvoker>(),
|
||||||
startFlowPermission<ContractUpgradeFlow<*, *>>()
|
startFlowPermission<ContractUpgradeFlow.Initiator<*, *>>(),
|
||||||
|
startFlowPermission<ContractUpgradeFlow.Acceptor>(),
|
||||||
|
startFlowPermission<ContractUpgradeFlow.Authorise>(),
|
||||||
|
startFlowPermission<ContractUpgradeFlow.Deauthorise>()
|
||||||
))
|
))
|
||||||
val rpcA = startProxy(a, user)
|
val rpcA = startProxy(a, user)
|
||||||
val rpcB = startProxy(b, user)
|
val rpcB = startProxy(b, user)
|
||||||
@ -141,18 +163,35 @@ class ContractUpgradeFlowTest {
|
|||||||
requireNotNull(atx)
|
requireNotNull(atx)
|
||||||
requireNotNull(btx)
|
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),
|
atx!!.tx.outRef<DummyContract.State>(0),
|
||||||
DummyContractV2::class.java).returnValue
|
DummyContractV2::class.java).returnValue
|
||||||
|
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
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.
|
// 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.
|
// 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),
|
atx.tx.outRef<DummyContract.State>(0),
|
||||||
DummyContractV2::class.java).returnValue
|
DummyContractV2::class.java).returnValue
|
||||||
|
|
||||||
@ -184,7 +223,7 @@ class ContractUpgradeFlowTest {
|
|||||||
val baseState = a.database.transaction { a.services.vaultQueryService.queryBy<ContractState>().states.single() }
|
val baseState = a.database.transaction { a.services.vaultQueryService.queryBy<ContractState>().states.single() }
|
||||||
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
||||||
// Starts contract upgrade flow.
|
// 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()
|
mockNet.runNetwork()
|
||||||
upgradeResult.getOrThrow()
|
upgradeResult.getOrThrow()
|
||||||
// Get contract state from the vault.
|
// Get contract state from the vault.
|
||||||
|
@ -29,6 +29,7 @@ class FinalityFlowTests {
|
|||||||
nodeB = nodes.partyNodes[1]
|
nodeB = nodes.partyNodes[1]
|
||||||
notary = nodes.notaryNode.info.notaryIdentity
|
notary = nodes.notaryNode.info.notaryIdentity
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
nodeA.ensureRegistered()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -41,10 +41,6 @@ class IdentitySyncFlowTests {
|
|||||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||||
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
||||||
val bob: Party = bobNode.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)
|
bobNode.registerInitiatedFlow(Receive::class.java)
|
||||||
|
|
||||||
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
|
// 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 issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notaryNode.services.myInfo.notaryIdentity))
|
||||||
val issueTx = issueFlow.resultFuture.getOrThrow().stx
|
val issueTx = issueFlow.resultFuture.getOrThrow().stx
|
||||||
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
|
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
|
// Run the flow to sync up the identities
|
||||||
aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).resultFuture.getOrThrow()
|
aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).resultFuture.getOrThrow()
|
||||||
val expected = aliceNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
val expected = aliceNode.database.transaction {
|
||||||
val actual = bobNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
aliceNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||||
|
}
|
||||||
|
val actual = bobNode.database.transaction {
|
||||||
|
bobNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||||
|
}
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ class ManualFinalityFlowTests {
|
|||||||
nodeC = nodes.partyNodes[2]
|
nodeC = nodes.partyNodes[2]
|
||||||
notary = nodes.notaryNode.info.notaryIdentity
|
notary = nodes.notaryNode.info.notaryIdentity
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
nodeA.ensureRegistered()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -26,10 +26,6 @@ class TransactionKeyFlowTests {
|
|||||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||||
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
||||||
val bob: Party = bobNode.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
|
// Run the flows
|
||||||
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
|
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
|
||||||
@ -44,8 +40,8 @@ class TransactionKeyFlowTests {
|
|||||||
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity)
|
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity)
|
||||||
|
|
||||||
// Verify that the anonymous identities look sane
|
// Verify that the anonymous identities look sane
|
||||||
assertEquals(alice.name, aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name)
|
assertEquals(alice.name, aliceNode.database.transaction { aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name })
|
||||||
assertEquals(bob.name, bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name)
|
assertEquals(bob.name, bobNode.database.transaction { bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name })
|
||||||
|
|
||||||
// Verify that the nodes have the right anonymous identities
|
// Verify that the nodes have the right anonymous identities
|
||||||
assertTrue { aliceAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
|
assertTrue { aliceAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
|
||||||
|
@ -3,10 +3,10 @@ package net.corda.core.identity
|
|||||||
import net.corda.core.crypto.entropyToKeyPair
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.core.utilities.getX500Name
|
||||||
import net.corda.testing.getTestPartyAndCertificate
|
import net.corda.testing.getTestPartyAndCertificate
|
||||||
import net.corda.testing.withTestSerialization
|
import net.corda.testing.withTestSerialization
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ class PartyAndCertificateTest {
|
|||||||
fun `kryo serialisation`() {
|
fun `kryo serialisation`() {
|
||||||
withTestSerialization {
|
withTestSerialization {
|
||||||
val original = getTestPartyAndCertificate(Party(
|
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))
|
entropyToKeyPair(BigInteger.valueOf(83)).public))
|
||||||
val copy = original.serialize().deserialize()
|
val copy = original.serialize().deserialize()
|
||||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.testing.ALICE
|
||||||
|
import net.corda.testing.BOB
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.AfterClass
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class AbstractAttachmentTest {
|
||||||
|
companion object {
|
||||||
|
private val dir = Files.createTempDirectory(AbstractAttachmentTest::class.simpleName)
|
||||||
|
private val bin = Paths.get(System.getProperty("java.home")).let { if (it.endsWith("jre")) it.parent else it } / "bin"
|
||||||
|
private val shredder = (dir / "_shredder").toFile() // No need to delete after each test.
|
||||||
|
fun execute(vararg command: String) {
|
||||||
|
assertEquals(0, ProcessBuilder()
|
||||||
|
.inheritIO()
|
||||||
|
.redirectOutput(shredder)
|
||||||
|
.directory(dir.toFile())
|
||||||
|
.command((bin / command[0]).toString(), *command.sliceArray(1 until command.size))
|
||||||
|
.start()
|
||||||
|
.waitFor())
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
@JvmStatic
|
||||||
|
fun beforeClass() {
|
||||||
|
execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", "alice", "-keypass", "alicepass", "-dname", ALICE.toString())
|
||||||
|
execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", "bob", "-keypass", "bobpass", "-dname", BOB.toString())
|
||||||
|
(dir / "_signable1").writeLines(listOf("signable1"))
|
||||||
|
(dir / "_signable2").writeLines(listOf("signable2"))
|
||||||
|
(dir / "_signable3").writeLines(listOf("signable3"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun load(name: String) = object : AbstractAttachment({ (dir / name).readAll() }) {
|
||||||
|
override val id get() = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
@JvmStatic
|
||||||
|
fun afterClass() {
|
||||||
|
dir.toFile().deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
dir.toFile().listFiles().forEach {
|
||||||
|
if (!it.name.startsWith("_")) it.deleteRecursively()
|
||||||
|
}
|
||||||
|
assertEquals(5, dir.toFile().listFiles().size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `empty jar has no signers`() {
|
||||||
|
(dir / "META-INF").createDirectory() // At least one arg is required, and jar cvf conveniently ignores this.
|
||||||
|
execute("jar", "cvf", "attachment.jar", "META-INF")
|
||||||
|
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||||
|
assertEquals(emptyList(), load("attachment.jar").signers) // There needs to have been a file for ALICE to sign.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unsigned jar has no signers`() {
|
||||||
|
execute("jar", "cvf", "attachment.jar", "_signable1")
|
||||||
|
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||||
|
execute("jar", "uvf", "attachment.jar", "_signable2")
|
||||||
|
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one signer`() {
|
||||||
|
execute("jar", "cvf", "attachment.jar", "_signable1", "_signable2")
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||||
|
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name }) // We only reused ALICE's distinguished name, so the keys will be different.
|
||||||
|
(dir / "my-dir").createDirectory()
|
||||||
|
execute("jar", "uvf", "attachment.jar", "my-dir")
|
||||||
|
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name }) // Unsigned directory is irrelevant.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `two signers`() {
|
||||||
|
execute("jar", "cvf", "attachment.jar", "_signable1", "_signable2")
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||||
|
assertEquals(listOf(ALICE.name, BOB.name), load("attachment.jar").signers.map { it.name })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a party must sign all the files in the attachment to be a signer`() {
|
||||||
|
execute("jar", "cvf", "attachment.jar", "_signable1")
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||||
|
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name })
|
||||||
|
execute("jar", "uvf", "attachment.jar", "_signable2")
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||||
|
assertEquals(listOf(BOB.name), load("attachment.jar").signers.map { it.name }) // ALICE hasn't signed the new file.
|
||||||
|
execute("jar", "uvf", "attachment.jar", "_signable3")
|
||||||
|
assertEquals(emptyList(), load("attachment.jar").signers) // Neither party has signed the new file.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bad signature is caught even if the party would not qualify as a signer`() {
|
||||||
|
(dir / "volatile").writeLines(listOf("volatile"))
|
||||||
|
execute("jar", "cvf", "attachment.jar", "volatile")
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||||
|
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name })
|
||||||
|
(dir / "volatile").writeLines(listOf("garbage"))
|
||||||
|
execute("jar", "uvf", "attachment.jar", "volatile", "_signable1") // ALICE's signature on volatile is now bad.
|
||||||
|
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||||
|
val a = load("attachment.jar")
|
||||||
|
// The JDK doesn't care that BOB has correctly signed the whole thing, it won't let us process the entry with ALICE's bad signature:
|
||||||
|
assertThatThrownBy { a.signers }.isInstanceOf(SecurityException::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -112,6 +112,18 @@ class CordaFutureTest {
|
|||||||
}
|
}
|
||||||
verify(log).error(any(), same(throwable))
|
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 {
|
class TransposeTest {
|
||||||
|
@ -2,14 +2,14 @@ package net.corda.core.node
|
|||||||
|
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
import net.corda.testing.getTestX509Name
|
import net.corda.core.utilities.getX500Name
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
class ServiceInfoTests {
|
class ServiceInfoTests {
|
||||||
val serviceType = ServiceType.getServiceType("test", "service").getSubType("subservice")
|
val serviceType = ServiceType.getServiceType("test", "service").getSubType("subservice")
|
||||||
val name = getTestX509Name("service.name")
|
val name = getX500Name(O = "service.name", L = "London", C = "GB")
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `type and name encodes correctly`() {
|
fun `type and name encodes correctly`() {
|
||||||
|
@ -46,13 +46,13 @@ private fun MockNetwork.MockNode.saveAttachment(content: String) = database.tran
|
|||||||
attachments.importAttachment(createAttachmentData(content).inputStream())
|
attachments.importAttachment(createAttachmentData(content).inputStream())
|
||||||
}
|
}
|
||||||
private fun MockNetwork.MockNode.hackAttachment(attachmentId: SecureHash, content: String) = database.transaction {
|
private fun MockNetwork.MockNode.hackAttachment(attachmentId: SecureHash, content: String) = database.transaction {
|
||||||
attachments.updateAttachment(attachmentId, createAttachmentData(content))
|
updateAttachment(attachmentId, createAttachmentData(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see NodeAttachmentService.importAttachment
|
* @see NodeAttachmentService.importAttachment
|
||||||
*/
|
*/
|
||||||
private fun NodeAttachmentService.updateAttachment(attachmentId: SecureHash, data: ByteArray) {
|
private fun updateAttachment(attachmentId: SecureHash, data: ByteArray) {
|
||||||
val session = DatabaseTransactionManager.current().session
|
val session = DatabaseTransactionManager.current().session
|
||||||
val attachment = session.get<NodeAttachmentService.DBAttachment>(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString())
|
val attachment = session.get<NodeAttachmentService.DBAttachment>(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString())
|
||||||
attachment?.let {
|
attachment?.let {
|
||||||
@ -73,6 +73,7 @@ class AttachmentSerializationTest {
|
|||||||
client = mockNet.createNode(server.network.myAddress)
|
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.
|
client.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
server.ensureRegistered()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -112,6 +113,7 @@ class AttachmentSerializationTest {
|
|||||||
|
|
||||||
private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment {
|
private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment {
|
||||||
override fun open() = throw UnsupportedOperationException("Not implemented.")
|
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) {
|
private class CustomAttachmentLogic(server: MockNetwork.MockNode, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(server) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.core.crypto
|
package net.corda.core.utilities
|
||||||
|
|
||||||
|
import net.corda.core.crypto.AddressFormatException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
@ -52,8 +52,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
private class CapturingTransientProperty(prefix: String) {
|
private class CapturingTransientProperty(val prefix: String, val seed: Long = random63BitValue()) {
|
||||||
private val seed = random63BitValue()
|
|
||||||
val transientVal by transient { prefix + seed + random63BitValue() }
|
val transientVal by transient { prefix + seed + random63BitValue() }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.core.utilities
|
package net.corda.core.utilities
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
@ -99,4 +100,33 @@ class LegalNameValidatorTest {
|
|||||||
validateLegalName("Legal Name With\n\rLine\nBreaks")
|
validateLegalName("Legal Name With\n\rLine\nBreaks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `validate x500Name`() {
|
||||||
|
validateX500Name(X500Name("O=Bank A, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||||
|
validateX500Name(X500Name("O=Bank A, L=New York, C=US, CN=Service Name"))
|
||||||
|
validateX500Name(X500Name("O=Bank A, L=New York, C=US"))
|
||||||
|
validateX500Name(X500Name("O=Bank A, L=New York, C=US"))
|
||||||
|
|
||||||
|
// Missing Organisation
|
||||||
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
|
validateX500Name(X500Name("L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||||
|
}
|
||||||
|
// Missing Locality
|
||||||
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
|
validateX500Name(X500Name("O=Bank A, C=US, OU=Org Unit, CN=Service Name"))
|
||||||
|
}
|
||||||
|
// Missing Country
|
||||||
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
|
validateX500Name(X500Name("O=Bank A, L=New York, OU=Org Unit, CN=Service Name"))
|
||||||
|
}
|
||||||
|
// Wrong organisation name format
|
||||||
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
|
validateX500Name(X500Name("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||||
|
}
|
||||||
|
// Wrong organisation name format
|
||||||
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
|
validateX500Name(X500Name("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -9,7 +9,18 @@ dokka {
|
|||||||
moduleName = 'corda'
|
moduleName = 'corda'
|
||||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin")
|
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin")
|
||||||
processConfigurations = ['compile']
|
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) {
|
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||||
@ -17,8 +28,19 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
|||||||
outputFormat = "javadoc"
|
outputFormat = "javadoc"
|
||||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
|
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
|
||||||
processConfigurations = ['compile']
|
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']
|
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'])
|
task buildDocs(dependsOn: ['apidocs', 'makeDocs'])
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user