mirror of
https://github.com/corda/corda.git
synced 2025-01-14 16:59:52 +00:00
Merge remote-tracking branch 'open/master' into aslemmer-enterprise-merge-september-8
This commit is contained in:
commit
c9e9242ff4
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
Thank you for choosing to report an issue with Corda.
|
||||
|
||||
When reporting an issue please make sure it contains;
|
||||
|
||||
* A clear description of the issue
|
||||
* Any logs or stack traces that you can provide (use a site like https://pastebin.com for larger logs)
|
||||
* Steps to reproduce the issue
|
||||
* The version/tag/release or commit hash it occured on
|
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
Thank you for choosing to contribute to Corda.
|
||||
|
||||
Your PR must be approved by one or more reviewers and all tests must be passed on TeamCity (https://ci.corda.r3cev.com) in order to be merged.
|
||||
|
||||
Once you have submitted a PR you are responsible for keeping it up to date until the time it is merged.
|
||||
|
||||
PR Checklist:
|
||||
|
||||
1. Ensure any new code is tested as described in https://docs.corda.net/testing.html
|
||||
2. Ensure you have done any relevant automated testing and manual testing
|
||||
3. Add your changes to docs/source/changelog.rst
|
||||
4. Update any documentation in docs/source relating to your changes and learn how to build them in https://docs.corda.net/building-the-docs.html
|
||||
5. If you are contributing for the first time please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you have read, and agreed to, the agreement.
|
||||
|
||||
Please remove this message when you have read it.
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ local.properties
|
||||
**/build/*
|
||||
|
||||
lib/dokka.jar
|
||||
lib/quasar.jar
|
||||
|
||||
**/logs/*
|
||||
|
||||
|
21
.idea/compiler.xml
generated
21
.idea/compiler.xml
generated
@ -62,8 +62,9 @@
|
||||
<module name="node-api_test" target="1.8" />
|
||||
<module name="node-capsule_main" target="1.6" />
|
||||
<module name="node-capsule_test" target="1.6" />
|
||||
<module name="node-schemas_main" target="1.8" />
|
||||
<module name="node-schemas_test" target="1.8" />
|
||||
<module name="node-driver_integrationTest" target="1.8" />
|
||||
<module name="node-driver_main" target="1.8" />
|
||||
<module name="node-driver_test" target="1.8" />
|
||||
<module name="node_integrationTest" target="1.8" />
|
||||
<module name="node_main" target="1.8" />
|
||||
<module name="node_smokeTest" target="1.8" />
|
||||
@ -91,13 +92,15 @@
|
||||
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
||||
<module name="simm-valuation-demo_main" target="1.8" />
|
||||
<module name="simm-valuation-demo_test" target="1.8" />
|
||||
<module name="smoke-test-utils_main" target="1.8" />
|
||||
<module name="smoke-test-utils_test" target="1.8" />
|
||||
<module name="test-common_main" target="1.8" />
|
||||
<module name="test-common_test" target="1.8" />
|
||||
<module name="test-utils_integrationTest" target="1.8" />
|
||||
<module name="test-utils_main" target="1.8" />
|
||||
<module name="test-utils_test" target="1.8" />
|
||||
<module name="testing-node-driver_integrationTest" target="1.8" />
|
||||
<module name="testing-node-driver_main" target="1.8" />
|
||||
<module name="testing-node-driver_test" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_main" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_test" target="1.8" />
|
||||
<module name="testing-test-common_main" target="1.8" />
|
||||
<module name="testing-test-common_test" target="1.8" />
|
||||
<module name="testing-test-utils_main" target="1.8" />
|
||||
<module name="testing-test-utils_test" target="1.8" />
|
||||
<module name="tools_main" target="1.8" />
|
||||
<module name="tools_test" target="1.8" />
|
||||
<module name="trader-demo_integrationTest" target="1.8" />
|
||||
|
18
build.gradle
18
build.gradle
@ -4,7 +4,7 @@ buildscript {
|
||||
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
||||
|
||||
// Our version: bump this on release.
|
||||
ext.corda_release_version = "0.15-SNAPSHOT"
|
||||
ext.corda_release_version = "0.16-SNAPSHOT"
|
||||
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
||||
// TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal
|
||||
ext.corda_platform_version = 1
|
||||
@ -143,10 +143,6 @@ allprojects {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
// TODO: remove this once we eliminate Exposed
|
||||
maven {
|
||||
url 'https://dl.bintray.com/kotlin/exposed'
|
||||
}
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
@ -220,15 +216,15 @@ tasks.withType(Test) {
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
directory "./build/nodes"
|
||||
networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB"
|
||||
networkMap "O=Controller,OU=corda,L=London,C=GB"
|
||||
node {
|
||||
name "CN=Controller,O=R3,OU=corda,L=London,C=GB"
|
||||
name "O=Controller,OU=corda,L=London,C=GB"
|
||||
advertisedServices = ["corda.notary.validating"]
|
||||
p2pPort 10002
|
||||
cordapps = []
|
||||
}
|
||||
node {
|
||||
name "CN=Bank A,O=R3,OU=corda,L=London,C=GB"
|
||||
name "O=Bank A,OU=corda,L=London,C=GB"
|
||||
advertisedServices = []
|
||||
p2pPort 10012
|
||||
rpcPort 10013
|
||||
@ -236,7 +232,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
cordapps = []
|
||||
}
|
||||
node {
|
||||
name "CN=Bank B,O=R3,OU=corda,L=London,C=GB"
|
||||
name "O=Bank B,OU=corda,L=London,C=GB"
|
||||
advertisedServices = []
|
||||
p2pPort 10007
|
||||
rpcPort 10008
|
||||
@ -256,7 +252,7 @@ bintrayConfig {
|
||||
projectUrl = 'https://github.com/corda/corda'
|
||||
gpgSign = true
|
||||
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
||||
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver']
|
||||
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver']
|
||||
license {
|
||||
name = 'Apache-2.0'
|
||||
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||
@ -291,7 +287,7 @@ artifactory {
|
||||
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
||||
}
|
||||
defaults {
|
||||
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver')
|
||||
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.jackson
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
@ -12,7 +12,7 @@ import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
@ -27,6 +27,8 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.parsePublicKeyBase58
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.math.BigDecimal
|
@ -1,11 +1,11 @@
|
||||
package net.corda.jackson
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
import com.google.common.collect.HashMultimap
|
||||
import com.google.common.collect.Multimap
|
||||
import net.corda.jackson.StringToMethodCallParser.ParsedMethodCall
|
||||
import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.Method
|
||||
@ -223,4 +223,4 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
|
||||
Pair(name, argStr)
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.jackson
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import net.corda.core.contracts.Amount
|
@ -1,4 +1,4 @@
|
||||
package net.corda.jackson
|
||||
package net.corda.client.jackson
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
@ -65,4 +65,4 @@ class StringToMethodCallParserTest {
|
||||
val args: Array<Any?> = parser.parseArguments(clazz.name, names.zip(ctor.parameterTypes), "alternativeWord: Foo bar!")
|
||||
assertArrayEquals(args, arrayOf<Any?>("Foo bar!"))
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ dependencies {
|
||||
|
||||
// Integration test helpers
|
||||
integrationTestCompile "junit:junit:$junit_version"
|
||||
integrationTestCompile project(':node-driver')
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
|
@ -58,13 +58,13 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
||||
startFlowPermission<CashPaymentFlow>(),
|
||||
startFlowPermission<CashExitFlow>())
|
||||
)
|
||||
val aliceNodeFuture = startNode(ALICE.name, rpcUsers = listOf(cashUser))
|
||||
val notaryNodeFuture = startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||
val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
|
||||
val notaryNodeFuture = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
|
||||
val notaryNodeHandle = notaryNodeFuture.getOrThrow()
|
||||
aliceNode = aliceNodeHandle.nodeInfo
|
||||
notaryNode = notaryNodeHandle.nodeInfo
|
||||
newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
|
||||
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
|
||||
val monitor = NodeMonitorModel()
|
||||
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
|
||||
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
|
||||
@ -76,7 +76,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
||||
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false)
|
||||
rpc = monitor.proxyObservable.value!!
|
||||
|
||||
val bobNodeHandle = startNode(BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
|
||||
val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
|
||||
bobNode = bobNodeHandle.nodeInfo
|
||||
val monitorBob = NodeMonitorModel()
|
||||
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
|
||||
|
@ -3,25 +3,30 @@ package net.corda.client.jfx.model
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.value.ObservableValue
|
||||
import net.corda.core.contracts.Amount
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.util.*
|
||||
|
||||
|
||||
interface ExchangeRate {
|
||||
fun rate(from: Currency, to: Currency): Double
|
||||
}
|
||||
|
||||
fun ExchangeRate.exchangeAmount(amount: Amount<Currency>, to: Currency) =
|
||||
Amount(exchangeDouble(amount, to).toLong(), to)
|
||||
|
||||
fun ExchangeRate.exchangeDouble(amount: Amount<Currency>, to: Currency) =
|
||||
rate(amount.token, to) * amount.quantity
|
||||
|
||||
/**
|
||||
* This model provides an exchange rate from arbitrary currency to arbitrary currency.
|
||||
* TODO hook up an actual oracle
|
||||
*/
|
||||
abstract class ExchangeRate {
|
||||
/**
|
||||
* Convert the given amount of a currency into the target currency.
|
||||
*
|
||||
* @return the original amount converted to an amount in the target currency.
|
||||
*/
|
||||
fun exchangeAmount(amount: Amount<Currency>, to: Currency) = Amount.fromDecimal(amount.toDecimal().multiply(rate(amount.token, to)), to)
|
||||
|
||||
abstract fun rate(from: Currency, to: Currency): BigDecimal
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of an exchange rate model, which uses a fixed exchange rate.
|
||||
*/
|
||||
// TODO hook up an actual oracle
|
||||
class ExchangeRateModel {
|
||||
val exchangeRate: ObservableValue<ExchangeRate> = SimpleObjectProperty<ExchangeRate>(object : ExchangeRate {
|
||||
override fun rate(from: Currency, to: Currency) = 1.0
|
||||
val exchangeRate: ObservableValue<ExchangeRate> = SimpleObjectProperty<ExchangeRate>(object : ExchangeRate() {
|
||||
override fun rate(from: Currency, to: Currency) = BigDecimal.ONE
|
||||
})
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import com.google.common.cache.CacheBuilder
|
||||
import com.google.common.cache.CacheLoader
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
||||
import net.corda.client.jfx.utils.firstOrDefault
|
||||
import net.corda.client.jfx.utils.firstOrNullObservable
|
||||
import net.corda.client.jfx.utils.fold
|
||||
import net.corda.client.jfx.utils.map
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import java.security.PublicKey
|
||||
@ -29,6 +30,12 @@ class NetworkIdentityModel {
|
||||
|
||||
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
||||
|
||||
private val identityCache = CacheBuilder.newBuilder()
|
||||
.build<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from {
|
||||
publicKey ->
|
||||
publicKey?.let { rpcProxy.map { it?.nodeIdentityFromParty(AnonymousParty(publicKey)) } }
|
||||
})
|
||||
|
||||
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { !it.isCordaService() }
|
||||
val notaries: ObservableList<NodeInfo> = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
|
||||
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
|
||||
@ -38,8 +45,5 @@ class NetworkIdentityModel {
|
||||
return advertisedServices.any { it.info.type.isNetworkMap() || it.info.type.isNotary() }
|
||||
}
|
||||
|
||||
// TODO: Use Identity Service in service hub instead?
|
||||
fun lookup(publicKey: PublicKey): ObservableValue<NodeInfo?> = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey.keys.any { it == publicKey } }) {
|
||||
it.legalIdentity.owningKey.keys.any { it == publicKey }
|
||||
}
|
||||
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ class NodeMonitorModel {
|
||||
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
|
||||
|
||||
// Transactions
|
||||
val (transactions, newTransactions) = proxy.verifiedTransactionsFeed()
|
||||
val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed()
|
||||
newTransactions.startWith(transactions).subscribe(transactionsSubject)
|
||||
|
||||
// SM -> TX mapping
|
||||
|
@ -29,7 +29,7 @@ object AmountBindings {
|
||||
return EasyBind.combine(observableCurrency, observableExchangeRate) { currency, exchangeRate ->
|
||||
Pair<Currency, (Amount<Currency>) -> Long>(
|
||||
currency,
|
||||
{ (quantity, _, token) -> (exchangeRate.rate(token, currency) * quantity).toLong() }
|
||||
{ amount -> exchangeRate.exchangeAmount(amount, currency).quantity }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ dependencies {
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
|
||||
testCompile project(':test-utils')
|
||||
testCompile project(':node-driver')
|
||||
testCompile project(':client:mock')
|
||||
|
||||
// Smoke tests do NOT have any Node code on the classpath!
|
||||
|
@ -6,12 +6,12 @@ import net.corda.core.messaging.CordaRPCOps;
|
||||
import net.corda.core.messaging.FlowHandle;
|
||||
import net.corda.core.node.NodeInfo;
|
||||
import net.corda.core.utilities.OpaqueBytes;
|
||||
import net.corda.core.utilities.X500NameUtils;
|
||||
import net.corda.finance.flows.AbstractCashFlow;
|
||||
import net.corda.finance.flows.CashIssueFlow;
|
||||
import net.corda.nodeapi.User;
|
||||
import net.corda.smoketesting.NodeConfig;
|
||||
import net.corda.smoketesting.NodeProcess;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -43,7 +43,7 @@ public class StandaloneCordaRPCJavaClientTest {
|
||||
private NodeInfo notaryNode;
|
||||
|
||||
private NodeConfig notaryConfig = new NodeConfig(
|
||||
new X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
|
||||
X500NameUtils.getX500Name("Notary Service", "Zurich", "CH"),
|
||||
port.getAndIncrement(),
|
||||
port.getAndIncrement(),
|
||||
port.getAndIncrement(),
|
||||
|
@ -9,10 +9,7 @@ import net.corda.core.messaging.*
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.SWISS_FRANCS
|
||||
@ -26,7 +23,6 @@ import net.corda.nodeapi.User
|
||||
import net.corda.smoketesting.NodeConfig
|
||||
import net.corda.smoketesting.NodeProcess
|
||||
import org.apache.commons.io.output.NullOutputStream
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -58,12 +54,12 @@ class StandaloneCordaRPClientTest {
|
||||
private lateinit var notaryNode: NodeInfo
|
||||
|
||||
private val notaryConfig = NodeConfig(
|
||||
legalName = X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"),
|
||||
p2pPort = port.andIncrement,
|
||||
rpcPort = port.andIncrement,
|
||||
webPort = port.andIncrement,
|
||||
extraServices = listOf("corda.notary.validating"),
|
||||
users = listOf(user)
|
||||
legalName = getX500Name(O = "Notary Service", OU = "corda", L = "Zurich", C = "CH"),
|
||||
p2pPort = port.andIncrement,
|
||||
rpcPort = port.andIncrement,
|
||||
webPort = port.andIncrement,
|
||||
extraServices = listOf("corda.notary.validating"),
|
||||
users = listOf(user)
|
||||
)
|
||||
|
||||
@Before
|
||||
|
@ -0,0 +1,64 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import com.esotericsoftware.kryo.KryoException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.rpcDriver
|
||||
import net.corda.testing.startRpcClient
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Test
|
||||
|
||||
class RPCFailureTests {
|
||||
class Unserializable
|
||||
interface Ops : RPCOps {
|
||||
fun getUnserializable(): Unserializable
|
||||
fun getUnserializableAsync(): CordaFuture<Unserializable>
|
||||
fun kotlinNPE()
|
||||
fun kotlinNPEAsync(): CordaFuture<Unit>
|
||||
}
|
||||
|
||||
class OpsImpl : Ops {
|
||||
override val protocolVersion = 1
|
||||
override fun getUnserializable() = Unserializable()
|
||||
override fun getUnserializableAsync(): CordaFuture<Unserializable> {
|
||||
return openFuture<Unserializable>().apply { capture { getUnserializable() } }
|
||||
}
|
||||
|
||||
override fun kotlinNPE() {
|
||||
(null as Any?)!!.hashCode()
|
||||
}
|
||||
|
||||
override fun kotlinNPEAsync(): CordaFuture<Unit> {
|
||||
return openFuture<Unit>().apply { capture { kotlinNPE() } }
|
||||
}
|
||||
}
|
||||
|
||||
private fun rpc(proc: (Ops) -> Any?): Unit = rpcDriver {
|
||||
val server = startRpcServer(ops = OpsImpl()).getOrThrow()
|
||||
proc(startRpcClient<Ops>(server.broker.hostAndPort!!).getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `kotlin NPE`() = rpc {
|
||||
assertThatThrownBy { it.kotlinNPE() }.isInstanceOf(KotlinNullPointerException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `kotlin NPE async`() = rpc {
|
||||
val future = it.kotlinNPEAsync()
|
||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KotlinNullPointerException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unserializable`() = rpc {
|
||||
assertThatThrownBy { it.getUnserializable() }.isInstanceOf(KryoException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unserializable async`() = rpc {
|
||||
val future = it.getUnserializableAsync()
|
||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KryoException::class.java)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
myLegalName : "CN=Bank A,O=Bank A,L=London,C=GB"
|
||||
myLegalName : "O=Bank A,L=London,C=GB"
|
||||
keyStorePassword : "cordacadevpass"
|
||||
trustStorePassword : "trustpass"
|
||||
p2pAddress : "localhost:10002"
|
||||
@ -7,6 +7,6 @@ webAddress : "localhost:10004"
|
||||
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
|
||||
networkMapService : {
|
||||
address : "localhost:10000"
|
||||
legalName : "CN=Network Map Service,O=R3,OU=corda,L=London,C=GB"
|
||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
||||
}
|
||||
useHTTPS : false
|
||||
|
@ -7,6 +7,6 @@ webAddress : "localhost:10007"
|
||||
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
|
||||
networkMapService : {
|
||||
address : "localhost:10000"
|
||||
legalName : "CN=Network Map Service,O=R3,OU=corda,L=London,C=GB"
|
||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
||||
}
|
||||
useHTTPS : false
|
||||
|
@ -1,4 +1,4 @@
|
||||
myLegalName : "CN=Notary Service,O=R3,OU=corda,L=London,C=GB"
|
||||
myLegalName : "O=Notary Service,OU=corda,L=London,C=GB"
|
||||
keyStorePassword : "cordacadevpass"
|
||||
trustStorePassword : "trustpass"
|
||||
p2pAddress : "localhost:10000"
|
||||
|
@ -1,5 +1,5 @@
|
||||
gradlePluginsVersion=0.15.1
|
||||
gradlePluginsVersion=0.16.1
|
||||
kotlinVersion=1.1.4
|
||||
guavaVersion=21.0
|
||||
bouncycastleVersion=1.57
|
||||
typesafeConfigVersion=1.3.1
|
||||
typesafeConfigVersion=1.3.1
|
||||
|
@ -22,12 +22,15 @@ dependencies {
|
||||
|
||||
// Bring in the MockNode infrastructure for writing protocol unit tests.
|
||||
testCompile project(":node")
|
||||
testCompile project(":test-utils")
|
||||
testCompile project(":node-driver")
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
// Quasar, for suspendable fibres.
|
||||
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
|
||||
|
||||
// Thread safety annotations
|
||||
compile "com.google.code.findbugs:jsr305:3.0.1"
|
||||
|
||||
@ -65,6 +68,15 @@ dependencies {
|
||||
compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
|
||||
}
|
||||
|
||||
// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)
|
||||
task copyQuasarJar(type: Copy) {
|
||||
from configurations.quasar
|
||||
into "$project.rootProject.projectDir/lib"
|
||||
rename { filename -> "quasar.jar"}
|
||||
}
|
||||
|
||||
jar.finalizedBy(copyQuasarJar)
|
||||
|
||||
configurations {
|
||||
testArtifacts.extendsFrom testRuntime
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.exactAdd
|
||||
|
42
core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
Normal file
42
core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
Normal file
@ -0,0 +1,42 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.extractFile
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
/**
|
||||
* An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
|
||||
* contain public static data which can be referenced from transactions and utilised from contracts. Good examples
|
||||
* of how attachments are meant to be used include:
|
||||
* - Calendar data
|
||||
* - Fixes (e.g. LIBOR)
|
||||
* - Smart contract code
|
||||
* - Legal documents
|
||||
* - Facts generated by oracles which might be reused a lot
|
||||
*/
|
||||
interface Attachment : NamedByHash {
|
||||
fun open(): InputStream
|
||||
fun openAsJAR(): JarInputStream {
|
||||
val stream = open()
|
||||
try {
|
||||
return JarInputStream(stream)
|
||||
} catch (t: Throwable) {
|
||||
stream.use { throw t }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the named file case insensitively and copies it to the output stream.
|
||||
* @throws FileNotFoundException if the given path doesn't exist in the attachment.
|
||||
*/
|
||||
fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }
|
||||
|
||||
/**
|
||||
* The parties that have correctly signed the whole attachment.
|
||||
* Can be empty, for example non-contract attachments won't be necessarily be signed.
|
||||
*/
|
||||
val signers: List<Party>
|
||||
}
|
@ -32,33 +32,33 @@ inline fun <R> requireThat(body: Requirements.() -> R) = Requirements.body()
|
||||
// TODO: Provide a version of select that interops with Java
|
||||
|
||||
/** Filters the command list by type, party and public key all at once. */
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signer: PublicKey? = null,
|
||||
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signer: PublicKey? = null,
|
||||
party: AbstractParty? = null) =
|
||||
filter { it.value is T }.
|
||||
filter { if (signer == null) true else signer in it.signers }.
|
||||
filter { if (party == null) true else party in it.signingParties }.
|
||||
map { AuthenticatedObject(it.signers, it.signingParties, it.value as T) }
|
||||
map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
|
||||
|
||||
// TODO: Provide a version of select that interops with Java
|
||||
|
||||
/** Filters the command list by type, parties and public keys all at once. */
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signers: Collection<PublicKey>?,
|
||||
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signers: Collection<PublicKey>?,
|
||||
parties: Collection<Party>?) =
|
||||
filter { it.value is T }.
|
||||
filter { if (signers == null) true else it.signers.containsAll(signers) }.
|
||||
filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
|
||||
map { AuthenticatedObject(it.signers, it.signingParties, it.value as T) }
|
||||
map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
|
||||
|
||||
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand() = try {
|
||||
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand() = try {
|
||||
select<T>().single()
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw IllegalStateException("Required ${T::class.qualifiedName} command") // Better error message.
|
||||
}
|
||||
|
||||
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
|
||||
fun <C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand(klass: Class<C>) =
|
||||
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject<C> else null }.single()
|
||||
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>) =
|
||||
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as CommandWithParties<C> else null }.single()
|
||||
|
||||
/**
|
||||
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
|
||||
@ -67,7 +67,7 @@ fun <C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingle
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
||||
commands: List<AuthenticatedObject<CommandData>>)
|
||||
commands: List<CommandWithParties<CommandData>>)
|
||||
: MoveCommand {
|
||||
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
|
||||
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
||||
|
@ -9,18 +9,11 @@ import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.MissingAttachmentsException
|
||||
import net.corda.core.serialization.SerializeAsTokenContext
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
/** Implemented by anything that can be named by a secure hash value (e.g. transactions, attachments). */
|
||||
interface NamedByHash {
|
||||
@ -219,11 +212,6 @@ interface LinearState : ContractState {
|
||||
* except at issuance/termination.
|
||||
*/
|
||||
val linearId: UniqueIdentifier
|
||||
|
||||
/**
|
||||
* True if this should be tracked by our vault(s).
|
||||
*/
|
||||
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
|
||||
}
|
||||
// DOCEND 2
|
||||
|
||||
@ -289,7 +277,7 @@ abstract class TypeOnlyCommandData : CommandData {
|
||||
data class Command<T : CommandData>(val value: T, val signers: List<PublicKey>) {
|
||||
// TODO Introduce NonEmptyList?
|
||||
init {
|
||||
require(signers.isNotEmpty())
|
||||
require(signers.isNotEmpty()) { "The list of signers cannot be empty" }
|
||||
}
|
||||
|
||||
constructor(data: T, key: PublicKey) : this(data, listOf(key))
|
||||
@ -312,9 +300,9 @@ interface MoveCommand : CommandData {
|
||||
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
|
||||
|
||||
// DOCSTART 6
|
||||
/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
|
||||
/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
|
||||
@CordaSerializable
|
||||
data class AuthenticatedObject<out T : Any>(
|
||||
data class CommandWithParties<out T : CommandData>(
|
||||
val signers: List<PublicKey>,
|
||||
/** If any public keys were recognised, the looked up institutions are available here */
|
||||
val signingParties: List<Party>,
|
||||
@ -367,69 +355,6 @@ interface UpgradedContract<in OldState : ContractState, out NewState : ContractS
|
||||
fun upgrade(state: OldState): NewState
|
||||
}
|
||||
|
||||
/**
|
||||
* An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
|
||||
* contain public static data which can be referenced from transactions and utilised from contracts. Good examples
|
||||
* of how attachments are meant to be used include:
|
||||
*
|
||||
* - Calendar data
|
||||
* - Fixes (e.g. LIBOR)
|
||||
* - Smart contract code
|
||||
* - Legal documents
|
||||
* - Facts generated by oracles which might be reused a lot
|
||||
*/
|
||||
interface Attachment : NamedByHash {
|
||||
|
||||
fun open(): InputStream
|
||||
|
||||
fun openAsJAR(): JarInputStream {
|
||||
val stream = open()
|
||||
try {
|
||||
return JarInputStream(stream)
|
||||
} catch (t: Throwable) {
|
||||
stream.use { throw t }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the named file case insensitively and copies it to the output stream.
|
||||
*
|
||||
* @throws FileNotFoundException if the given path doesn't exist in the attachment.
|
||||
*/
|
||||
fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }
|
||||
}
|
||||
|
||||
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
|
||||
companion object {
|
||||
fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
|
||||
return {
|
||||
val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
|
||||
(a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected val attachmentData: ByteArray by lazy(dataLoader)
|
||||
override fun open(): InputStream = attachmentData.inputStream()
|
||||
override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
|
||||
override fun hashCode() = id.hashCode()
|
||||
override fun toString() = "${javaClass.simpleName}(id=$id)"
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
|
||||
val p = path.toLowerCase().split('\\', '/')
|
||||
while (true) {
|
||||
val e = nextJarEntry ?: break
|
||||
if (!e.isDirectory && e.name.toLowerCase().split('\\', '/') == p) {
|
||||
copyTo(outputTo)
|
||||
return
|
||||
}
|
||||
closeEntry()
|
||||
}
|
||||
throw FileNotFoundException(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot
|
||||
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
|
||||
|
@ -1,12 +1,8 @@
|
||||
package net.corda.core.crypto.composite
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.crypto.provider.CordaObjectIdentifier
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.utilities.exactAdd
|
||||
import net.corda.core.crypto.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.exactAdd
|
||||
import net.corda.core.utilities.sequence
|
||||
import org.bouncycastle.asn1.*
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||
@ -15,8 +11,10 @@ import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A tree data structure that enables the representation of composite public keys.
|
||||
* Notice that with that implementation CompositeKey extends PublicKey. Leaves are represented by single public keys.
|
||||
* A tree data structure that enables the representation of composite public keys, which are used to represent
|
||||
* the signing requirements for multisignature scenarios such as RAFT notary services. A composite key is a list
|
||||
* of leaf keys and their contributing weight, and each leaf can be a conventional single key or a composite key.
|
||||
* Keys contribute their weight to the total if they are matched by the signature.
|
||||
*
|
||||
* For complex scenarios, such as *"Both Alice and Bob need to sign to consume a state S"*, we can represent
|
||||
* the requirement by creating a tree with a root [CompositeKey], and Alice and Bob as children.
|
||||
@ -26,9 +24,7 @@ import java.util.*
|
||||
* Using these constructs we can express e.g. 1 of N (OR) or N of N (AND) signature requirements. By nesting we can
|
||||
* create multi-level requirements such as *"either the CEO or 3 of 5 of his assistants need to sign"*.
|
||||
*
|
||||
* [CompositeKey] maintains a list of [NodeAndWeight]s which holds child subtree with associated weight carried by child node signatures.
|
||||
*
|
||||
* The [threshold] specifies the minimum total weight required (in the simple case – the minimum number of child
|
||||
* @property threshold specifies the minimum total weight required (in the simple case – the minimum number of child
|
||||
* signatures required) to satisfy the sub-tree rooted at this node.
|
||||
*/
|
||||
@CordaSerializable
|
||||
@ -163,7 +159,7 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes single PublicKey and checks if CompositeKey requirements hold for that key.
|
||||
* Takes single [PublicKey] and checks if [CompositeKey] requirements hold for that key.
|
||||
*/
|
||||
fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
|
||||
|
||||
@ -209,7 +205,7 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of all leaf keys of that CompositeKey.
|
||||
* Set of all leaf keys of that [CompositeKey].
|
||||
*/
|
||||
val leafKeys: Set<PublicKey>
|
||||
get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
|
||||
@ -253,9 +249,14 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
|
||||
* the total (aggregated) weight of the children, effectively generating an "N of N" requirement.
|
||||
* During process removes single keys wrapped in [CompositeKey] and enforces ordering on child nodes.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* @param threshold specifies the minimum total weight required (in the simple case – the minimum number of child
|
||||
* signatures required) to satisfy the sub-tree rooted at this node.
|
||||
* @throws IllegalArgumentException if the threshold value is invalid.
|
||||
* @throws IllegalStateException if the composite key that would be generated from the current state of the builder
|
||||
* is invalid (for example it would contain no keys).
|
||||
*/
|
||||
fun build(threshold: Int? = null): PublicKey {
|
||||
require(threshold == null || threshold > 0)
|
||||
val n = children.size
|
||||
return if (n > 1)
|
||||
CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children)
|
||||
@ -265,15 +266,7 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
|
||||
// Returning the only child node which is [PublicKey] itself. We need to avoid single-key [CompositeKey] instances,
|
||||
// as there are scenarios where developers expected the underlying key and its composite versions to be equivalent.
|
||||
children.first().node
|
||||
} else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
|
||||
} else throw IllegalStateException("Trying to build CompositeKey without child nodes.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands all [CompositeKey]s present in PublicKey iterable to set of single [PublicKey]s.
|
||||
* If an element of the set is a single PublicKey it gives just that key, if it is a [CompositeKey] it returns all leaf
|
||||
* keys for that composite element.
|
||||
*/
|
||||
val Iterable<PublicKey>.expandedCompositeKeys: Set<PublicKey>
|
||||
get() = flatMap { it.keys }.toSet()
|
@ -1,11 +1,14 @@
|
||||
package net.corda.core.crypto.composite
|
||||
package net.corda.core.crypto
|
||||
|
||||
import java.security.*
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import java.security.spec.KeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
class KeyFactory : KeyFactorySpi() {
|
||||
/**
|
||||
* Factory for generating composite keys from ASN.1 format key specifications. This is used by [CordaSecurityProvider].
|
||||
*/
|
||||
class CompositeKeyFactory : KeyFactorySpi() {
|
||||
|
||||
@Throws(InvalidKeySpecException::class)
|
||||
override fun engineGeneratePrivate(keySpec: KeySpec): PrivateKey {
|
||||
@ -23,7 +26,7 @@ class KeyFactory : KeyFactorySpi() {
|
||||
|
||||
@Throws(InvalidKeySpecException::class)
|
||||
override fun <T : KeySpec> engineGetKeySpec(key: Key, keySpec: Class<T>): T {
|
||||
// Only support [X509EncodedKeySpec].
|
||||
// Only support X509EncodedKeySpec.
|
||||
throw InvalidKeySpecException("Not implemented yet $key $keySpec")
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.core.crypto.composite
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
|
||||
import net.corda.core.serialization.deserialize
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.*
|
||||
@ -27,6 +27,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
|
||||
return signatureState!!
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in inherited API")
|
||||
@Throws(InvalidAlgorithmParameterException::class)
|
||||
override fun engineGetParameter(param: String?): Any {
|
||||
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
|
||||
@ -46,6 +47,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in inherited API")
|
||||
@Throws(InvalidAlgorithmParameterException::class)
|
||||
override fun engineSetParameter(param: String?, value: Any?) {
|
||||
throw InvalidAlgorithmParameterException("Composite signatures do not support any parameters")
|
@ -1,7 +1,5 @@
|
||||
package net.corda.core.crypto.provider
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeSignature
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import java.security.AccessController
|
||||
import java.security.PrivilegedAction
|
||||
@ -17,20 +15,22 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
|
||||
}
|
||||
|
||||
private fun setup() {
|
||||
put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.composite.KeyFactory")
|
||||
put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.composite.CompositeSignature")
|
||||
put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.CompositeKeyFactory")
|
||||
put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.CompositeSignature")
|
||||
|
||||
val compositeKeyOID = CordaObjectIdentifier.COMPOSITE_KEY.id
|
||||
put("Alg.Alias.KeyFactory.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
|
||||
put("Alg.Alias.KeyFactory.OID.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
|
||||
put("Alg.Alias.Signature.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
|
||||
put("Alg.Alias.Signature.OID.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
|
||||
|
||||
val compositeSignatureOID = CordaObjectIdentifier.COMPOSITE_SIGNATURE.id
|
||||
put("Alg.Alias.Signature.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM)
|
||||
put("Alg.Alias.Signature.OID.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM)
|
||||
}
|
||||
}
|
||||
|
||||
object CordaObjectIdentifier {
|
||||
// UUID-based OID
|
||||
// TODO: Register for an OID space and issue our own shorter OID
|
||||
// TODO: Register for an OID space and issue our own shorter OID.
|
||||
@JvmField val COMPOSITE_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
|
||||
@JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeSignature
|
||||
import net.corda.core.crypto.provider.CordaObjectIdentifier
|
||||
import net.corda.core.crypto.provider.CordaSecurityProvider
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
@ -201,7 +197,7 @@ object Crypto {
|
||||
private val providerMap: Map<String, Provider> = mapOf(
|
||||
BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(),
|
||||
CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(),
|
||||
"BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
|
||||
"BCPQC" to BouncyCastlePQCProvider()) // Unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
|
||||
|
||||
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
|
||||
putAll(EdDSASecurityProvider())
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toBase58
|
||||
import net.corda.core.utilities.toSHA256Bytes
|
||||
import java.math.BigInteger
|
||||
import net.corda.core.utilities.SgxSupport
|
||||
import java.security.*
|
||||
@ -17,13 +18,9 @@ import java.security.*
|
||||
* @throws SignatureException if signing is not possible due to malformed data or private key.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
|
||||
fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature {
|
||||
return DigitalSignature(Crypto.doSign(this, bytesToSign))
|
||||
}
|
||||
fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign))
|
||||
|
||||
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
||||
return DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
|
||||
}
|
||||
fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes)
|
||||
|
||||
/**
|
||||
* Helper function to sign with a key pair.
|
||||
@ -81,32 +78,27 @@ fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature) : Boolean
|
||||
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
|
||||
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
|
||||
|
||||
val PublicKey.keys: Set<PublicKey> get() {
|
||||
return if (this is CompositeKey) this.leafKeys
|
||||
else setOf(this)
|
||||
}
|
||||
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
|
||||
|
||||
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
|
||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean {
|
||||
return if (this is CompositeKey) this.isFulfilledBy(otherKeys)
|
||||
else this in otherKeys
|
||||
}
|
||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
|
||||
|
||||
/** Checks whether any of the given [keys] matches a leaf on the CompositeKey tree or a single PublicKey */
|
||||
/** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */
|
||||
fun PublicKey.containsAny(otherKeys: Iterable<PublicKey>): Boolean {
|
||||
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
|
||||
else this in otherKeys
|
||||
}
|
||||
|
||||
/** Returns the set of all [PublicKey]s of the signatures */
|
||||
/** Returns the set of all [PublicKey]s of the signatures. */
|
||||
fun Iterable<TransactionSignature>.byKeys() = map { it.by }.toSet()
|
||||
|
||||
// Allow Kotlin destructuring: val (private, public) = keyPair
|
||||
// Allow Kotlin destructuring:
|
||||
// val (private, public) = keyPair
|
||||
operator fun KeyPair.component1(): PrivateKey = this.private
|
||||
|
||||
operator fun KeyPair.component2(): PublicKey = this.public
|
||||
|
||||
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
|
||||
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */
|
||||
fun generateKeyPair(): KeyPair = Crypto.generateKeyPair()
|
||||
|
||||
/**
|
||||
@ -147,13 +139,11 @@ fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Cr
|
||||
* @param numOfBytes how many random bytes to output.
|
||||
* @return a random [ByteArray].
|
||||
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
|
||||
* or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
|
||||
* or if no strong [SecureRandom] implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
|
||||
* which should never happen and suggests an unusual JVM or non-standard Java library.
|
||||
*/
|
||||
@Throws(NoSuchAlgorithmException::class)
|
||||
fun secureRandomBytes(numOfBytes: Int): ByteArray {
|
||||
return newSecureRandom().generateSeed(numOfBytes)
|
||||
}
|
||||
fun secureRandomBytes(numOfBytes: Int): ByteArray = newSecureRandom().generateSeed(numOfBytes)
|
||||
|
||||
/**
|
||||
* This is a hack added because during deserialisation when no-param constructors are called sometimes default values
|
||||
|
@ -6,8 +6,8 @@ import java.security.InvalidKeyException
|
||||
import java.security.PublicKey
|
||||
import java.security.SignatureException
|
||||
|
||||
// TODO: Is there a use-case for bare [DigitalSignature], or is everything a [DigitalSignature.WithKey]? If there's no
|
||||
// actual use-case, we should merge the with key version into the parent class. In that case [CompositeSignatureWithKeys]
|
||||
// TODO: Is there a use-case for bare DigitalSignature, or is everything a DigitalSignature.WithKey? If there's no
|
||||
// actual use-case, we should merge the with key version into the parent class. In that case CompositeSignatureWithKeys
|
||||
// should be renamed to match.
|
||||
/** A wrapper around a digital signature. */
|
||||
@CordaSerializable
|
||||
|
@ -3,7 +3,7 @@ package net.corda.core.crypto
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Creation and verification of a Merkle Tree for a Wire Transaction.
|
||||
* Creation and verification of a Merkle tree for a [WireTransaction].
|
||||
*
|
||||
* See: https://en.wikipedia.org/wiki/Merkle_tree
|
||||
*
|
||||
@ -49,7 +49,7 @@ sealed class MerkleTree {
|
||||
*/
|
||||
private tailrec fun buildMerkleTree(lastNodesList: List<MerkleTree>): MerkleTree {
|
||||
if (lastNodesList.size == 1) {
|
||||
return lastNodesList[0] //Root reached.
|
||||
return lastNodesList[0] // Root reached.
|
||||
} else {
|
||||
val newLevelHashes: MutableList<MerkleTree> = ArrayList()
|
||||
val n = lastNodesList.size
|
||||
|
22
core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
Normal file
22
core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.security.PublicKey
|
||||
|
||||
object NullKeys {
|
||||
@CordaSerializable
|
||||
object NullPublicKey : PublicKey, Comparable<PublicKey> {
|
||||
override fun getAlgorithm() = "NULL"
|
||||
override fun getEncoded() = byteArrayOf(0)
|
||||
override fun getFormat() = "NULL"
|
||||
override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
|
||||
override fun toString() = "NULL_KEY"
|
||||
}
|
||||
|
||||
val NULL_PARTY = AnonymousParty(NullPublicKey)
|
||||
|
||||
/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
|
||||
val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
|
||||
|
||||
}
|
@ -12,7 +12,7 @@ import java.security.MessageDigest
|
||||
*/
|
||||
@CordaSerializable
|
||||
sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
/** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes) */
|
||||
/** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes). */
|
||||
class SHA256(bytes: ByteArray) : SecureHash(bytes) {
|
||||
init {
|
||||
require(bytes.size == 32)
|
||||
|
@ -11,4 +11,3 @@ import net.corda.core.serialization.CordaSerializable
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)
|
||||
|
||||
|
@ -29,4 +29,5 @@ data class SignatureScheme(
|
||||
val signatureName: String,
|
||||
val algSpec: AlgorithmParameterSpec?,
|
||||
val keySize: Int?,
|
||||
val desc: String)
|
||||
val desc: String
|
||||
)
|
||||
|
@ -1,78 +0,0 @@
|
||||
@file:JvmName("X500NameUtils")
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
|
||||
* adds one.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
|
||||
*
|
||||
* @param mutator a function to generate the new value from the previous one.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
|
||||
val builder = X500NameBuilder(BCStyle.INSTANCE)
|
||||
var matched = false
|
||||
this.rdNs.forEach { rdn ->
|
||||
rdn.typesAndValues.forEach { typeAndValue ->
|
||||
when (typeAndValue.type) {
|
||||
BCStyle.CN -> {
|
||||
matched = true
|
||||
builder.addRDN(typeAndValue.type, mutator(typeAndValue.value))
|
||||
}
|
||||
else -> {
|
||||
builder.addRDN(typeAndValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
require(matched) { "Input X.500 name must include a common name (CN) attribute: ${this}" }
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
|
||||
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
|
||||
val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
|
||||
val X500Name.locationOrNull: String? get() = try {
|
||||
location
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
|
||||
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
|
||||
|
||||
/**
|
||||
* Generate a distinguished name from the provided values.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun getX509Name(myLegalName: String, nearestCity: String, email: String, country: String? = null): X500Name {
|
||||
return X500NameBuilder(BCStyle.INSTANCE).let { builder ->
|
||||
builder.addRDN(BCStyle.CN, myLegalName)
|
||||
builder.addRDN(BCStyle.L, nearestCity)
|
||||
country?.let { builder.addRDN(BCStyle.C, it) }
|
||||
builder.addRDN(BCStyle.E, email)
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
@ -1,21 +0,0 @@
|
||||
package net.corda.core.crypto.testing
|
||||
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.security.PublicKey
|
||||
|
||||
@CordaSerializable
|
||||
object NullPublicKey : PublicKey, Comparable<PublicKey> {
|
||||
override fun getAlgorithm() = "NULL"
|
||||
override fun getEncoded() = byteArrayOf(0)
|
||||
override fun getFormat() = "NULL"
|
||||
override fun compareTo(other: PublicKey): Int = if (other == NullPublicKey) 0 else -1
|
||||
override fun toString() = "NULL_KEY"
|
||||
}
|
||||
|
||||
val NULL_PARTY = AnonymousParty(NullPublicKey)
|
||||
|
||||
/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
|
||||
val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))
|
@ -138,6 +138,7 @@ abstract class AbstractStateReplacementFlow {
|
||||
// We use Void? instead of Unit? as that's what you'd use in Java.
|
||||
abstract class Acceptor<in T>(val otherSide: Party,
|
||||
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
|
||||
constructor(otherSide: Party) : this(otherSide, Acceptor.tracker())
|
||||
companion object {
|
||||
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
||||
object APPROVING : ProgressTracker.Step("State replacement approved")
|
||||
|
@ -3,7 +3,7 @@ package net.corda.core.flows
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
|
@ -1,72 +1,146 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* A flow to be used for upgrading state objects of an old contract to a new contract.
|
||||
* A flow to be used for authorising and upgrading state objects of an old contract to a new contract.
|
||||
*
|
||||
* This assembles the transaction for contract upgrade and sends out change proposals to all participants
|
||||
* of that state. If participants agree to the proposed change, they each sign the transaction.
|
||||
* Finally, the transaction containing all signatures is sent back to each participant so they can record it and
|
||||
* use the new updated state for future transactions.
|
||||
*/
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
|
||||
originalState: StateAndRef<OldState>,
|
||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
||||
object ContractUpgradeFlow {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun verify(tx: LedgerTransaction) {
|
||||
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||
verify(
|
||||
tx.inputStates.single(),
|
||||
tx.outputStates.single(),
|
||||
tx.commandsOfType<UpgradeCommand>().single())
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
|
||||
* Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using the [UpgradedContract] class.
|
||||
* This method will NOT initiate the upgrade process. To start the upgrade process, see [Initiator].
|
||||
*/
|
||||
@StartableByRPC
|
||||
class Authorise(
|
||||
val stateAndRef: StateAndRef<*>,
|
||||
private val upgradedContractClass: Class<out UpgradedContract<*, *>>
|
||||
) : FlowLogic<Void?>() {
|
||||
@Suspendable
|
||||
override fun call(): Void? {
|
||||
val upgrade = upgradedContractClass.newInstance()
|
||||
if (upgrade.legacyContract != stateAndRef.state.data.contract.javaClass) {
|
||||
throw FlowException("The contract state cannot be upgraded using provided UpgradedContract.")
|
||||
}
|
||||
serviceHub.contractUpgradeService.storeAuthorisedContractUpgrade(stateAndRef.ref, upgradedContractClass)
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun verify(input: ContractState, output: ContractState, commandData: Command<UpgradeCommand>) {
|
||||
val command = commandData.value
|
||||
val participantKeys: Set<PublicKey> = input.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
|
||||
requireThat {
|
||||
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
|
||||
"Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
|
||||
"Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
|
||||
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
|
||||
}
|
||||
|
||||
/**
|
||||
* Deauthorise a contract state upgrade.
|
||||
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
|
||||
*/
|
||||
@StartableByRPC
|
||||
class Deauthorise(
|
||||
val stateRef: StateRef
|
||||
) : FlowLogic< Void?>() {
|
||||
@Suspendable
|
||||
override fun call(): Void? {
|
||||
serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class Initiator<OldState : ContractState, out NewState : ContractState>(
|
||||
originalState: StateAndRef<OldState>,
|
||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
||||
|
||||
companion object {
|
||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||
stateRef: StateAndRef<OldState>,
|
||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||
privacySalt: PrivacySalt
|
||||
): TransactionBuilder {
|
||||
val contractUpgrade = upgradedContractClass.newInstance()
|
||||
return TransactionBuilder(stateRef.state.notary)
|
||||
.withItems(
|
||||
stateRef,
|
||||
contractUpgrade.upgrade(stateRef.state.data),
|
||||
Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }),
|
||||
privacySalt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||
stateRef: StateAndRef<OldState>,
|
||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||
privacySalt: PrivacySalt
|
||||
): TransactionBuilder {
|
||||
val contractUpgrade = upgradedContractClass.newInstance()
|
||||
return TransactionBuilder(stateRef.state.notary)
|
||||
.withItems(
|
||||
stateRef,
|
||||
contractUpgrade.upgrade(stateRef.state.data),
|
||||
Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }),
|
||||
privacySalt
|
||||
)
|
||||
@Suspendable
|
||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||
val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
|
||||
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
||||
// TODO: We need a much faster way of finding our key in the transaction
|
||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
||||
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
||||
}
|
||||
}
|
||||
|
||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||
val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
|
||||
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
||||
// TODO: We need a much faster way of finding our key in the transaction
|
||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
||||
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
||||
@StartableByRPC
|
||||
@InitiatedBy(ContractUpgradeFlow.Initiator::class)
|
||||
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun verify(tx: LedgerTransaction) {
|
||||
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||
verify(tx.inputStates.single(),
|
||||
tx.outputStates.single(),
|
||||
tx.commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun verify(input: ContractState, output: ContractState, commandData: Command<UpgradeCommand>) {
|
||||
val command = commandData.value
|
||||
val participantKeys: Set<PublicKey> = input.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
|
||||
requireThat {
|
||||
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
|
||||
"Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
|
||||
"Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
|
||||
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@Throws(StateReplacementException::class)
|
||||
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal<Class<out UpgradedContract<ContractState, *>>>) {
|
||||
// Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and
|
||||
// verify outputs matches the proposed upgrade.
|
||||
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
|
||||
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
|
||||
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
||||
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
||||
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||
val proposedTx = stx.tx
|
||||
val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
||||
requireThat {
|
||||
"The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
|
||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||
}
|
||||
ContractUpgradeFlow.Acceptor.verify(
|
||||
oldStateAndRef.state.data,
|
||||
expectedTx.outRef<ContractState>(0).state.data,
|
||||
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ object NotaryFlow {
|
||||
val tx: Any = if (stx.isNotaryChangeTransaction()) {
|
||||
stx.notaryChangeTx
|
||||
} else {
|
||||
stx.tx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
|
||||
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
|
||||
}
|
||||
sendAndReceiveWithRetry(notaryParty, tx)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package net.corda.core.identity
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
|
@ -0,0 +1,69 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.MissingAttachmentsException
|
||||
import net.corda.core.serialization.SerializeAsTokenContext
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.security.CodeSigner
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
|
||||
companion object {
|
||||
fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
|
||||
return {
|
||||
val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
|
||||
(a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
|
||||
}
|
||||
}
|
||||
|
||||
/** @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File> */
|
||||
private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex()
|
||||
private val shredder = ByteArray(1024)
|
||||
}
|
||||
|
||||
protected val attachmentData: ByteArray by lazy(dataLoader)
|
||||
override fun open(): InputStream = attachmentData.inputStream()
|
||||
override val signers by lazy {
|
||||
// Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":
|
||||
var attachmentSigners: MutableSet<CodeSigner>? = null
|
||||
openAsJAR().use { jar ->
|
||||
while (true) {
|
||||
val entry = jar.nextJarEntry ?: break
|
||||
if (entry.isDirectory || unsignableEntryName.matches(entry.name)) continue
|
||||
while (jar.read(shredder) != -1) { // Must read entry fully for codeSigners to be valid.
|
||||
// Do nothing.
|
||||
}
|
||||
val entrySigners = entry.codeSigners ?: emptyArray()
|
||||
attachmentSigners?.retainAll(entrySigners) ?: run { attachmentSigners = entrySigners.toMutableSet() }
|
||||
if (attachmentSigners!!.isEmpty()) break // Performance short-circuit.
|
||||
}
|
||||
}
|
||||
(attachmentSigners ?: emptySet<CodeSigner>()).map {
|
||||
Party(it.signerCertPath.certificates[0].toX509CertHolder())
|
||||
}.sortedBy { it.name.toString() } // Determinism.
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
|
||||
override fun hashCode() = id.hashCode()
|
||||
override fun toString() = "${javaClass.simpleName}(id=$id)"
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
|
||||
fun String.norm() = toLowerCase().split('\\', '/') // XXX: Should this really be locale-sensitive?
|
||||
val p = path.norm()
|
||||
while (true) {
|
||||
val e = nextJarEntry ?: break
|
||||
if (!e.isDirectory && e.name.norm() == p) {
|
||||
copyTo(outputTo)
|
||||
return
|
||||
}
|
||||
closeEntry()
|
||||
}
|
||||
throw FileNotFoundException(path)
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.AbstractAttachment
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.NamedByHash
|
||||
import net.corda.core.crypto.SecureHash
|
||||
|
@ -123,11 +123,11 @@ interface CordaRPCOps : RPCOps {
|
||||
*
|
||||
* Generic vault query function which takes a [QueryCriteria] object to define filters,
|
||||
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
|
||||
* and returns a [Vault.PageAndUpdates] object containing
|
||||
* 1) a snapshot as a [Vault.Page] (described previously in [queryBy])
|
||||
* and returns a [DataFeed] object containing
|
||||
* 1) a snapshot as a [Vault.Page] (described previously in [CordaRPCOps.vaultQueryBy])
|
||||
* 2) an [Observable] of [Vault.Update]
|
||||
*
|
||||
* Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
|
||||
* Notes: the snapshot part of the query adheres to the same behaviour as the [CordaRPCOps.vaultQueryBy] function.
|
||||
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
|
||||
*/
|
||||
// DOCSTART VaultTrackByAPI
|
||||
@ -157,15 +157,21 @@ interface CordaRPCOps : RPCOps {
|
||||
// DOCEND VaultTrackAPIHelpers
|
||||
|
||||
/**
|
||||
* Returns a list of all recorded transactions.
|
||||
* @suppress Returns a list of all recorded transactions.
|
||||
*
|
||||
* TODO This method should be removed once SGX work is finalised and the design of the corresponding API using [FilteredTransaction] can be started
|
||||
*/
|
||||
fun verifiedTransactionsSnapshot(): List<SignedTransaction>
|
||||
@Deprecated("This method is intended only for internal use and will be removed from the public API soon.")
|
||||
fun internalVerifiedTransactionsSnapshot(): List<SignedTransaction>
|
||||
|
||||
/**
|
||||
* Returns a data feed of all recorded transactions and an observable of future recorded ones.
|
||||
* @suppress Returns a data feed of all recorded transactions and an observable of future recorded ones.
|
||||
*
|
||||
* TODO This method should be removed once SGX work is finalised and the design of the corresponding API using [FilteredTransaction] can be started
|
||||
*/
|
||||
@Deprecated("This method is intended only for internal use and will be removed from the public API soon.")
|
||||
@RPCReturnsObservables
|
||||
fun verifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction>
|
||||
fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction>
|
||||
|
||||
/**
|
||||
* Returns a snapshot list of existing state machine id - recorded transaction hash mappings.
|
||||
@ -194,14 +200,14 @@ interface CordaRPCOps : RPCOps {
|
||||
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
|
||||
*/
|
||||
@RPCReturnsObservables
|
||||
fun <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
|
||||
fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
|
||||
|
||||
/**
|
||||
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
|
||||
* result of running the flow. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
|
||||
*/
|
||||
@RPCReturnsObservables
|
||||
fun <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
|
||||
fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
|
||||
|
||||
/**
|
||||
* Returns Node's identity, assuming this will not change while the node is running.
|
||||
@ -233,20 +239,6 @@ interface CordaRPCOps : RPCOps {
|
||||
*/
|
||||
fun uploadAttachment(jar: InputStream): SecureHash
|
||||
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
|
||||
* Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
|
||||
* This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
|
||||
*/
|
||||
fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>)
|
||||
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will remove the upgrade authorisation from the vault.
|
||||
*/
|
||||
fun deauthoriseContractUpgrade(state: StateAndRef<*>)
|
||||
|
||||
/**
|
||||
* Returns the node's current time.
|
||||
*/
|
||||
@ -257,7 +249,7 @@ interface CordaRPCOps : RPCOps {
|
||||
* complete with an exception if it is unable to.
|
||||
*/
|
||||
@RPCReturnsObservables
|
||||
fun waitUntilRegisteredWithNetworkMap(): CordaFuture<Void?>
|
||||
fun waitUntilNetworkReady(): CordaFuture<Void?>
|
||||
|
||||
// TODO These need rethinking. Instead of these direct calls we should have a way of replicating a subset of
|
||||
// the node's state locally and query that directly.
|
||||
@ -299,6 +291,11 @@ interface CordaRPCOps : RPCOps {
|
||||
* @return the node info if available.
|
||||
*/
|
||||
fun nodeIdentityFromParty(party: AbstractParty): NodeInfo?
|
||||
|
||||
/**
|
||||
* Clear all network map data from local node cache.
|
||||
*/
|
||||
fun clearNetworkMapCache()
|
||||
}
|
||||
|
||||
inline fun <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
|
||||
@ -322,25 +319,25 @@ inline fun <reified T : ContractState> CordaRPCOps.vaultTrackBy(criteria: QueryC
|
||||
* Note that the passed in constructor function is only used for unification of other type parameters and reification of
|
||||
* the Class instance of the flow. This could be changed to use the constructor function directly.
|
||||
*/
|
||||
inline fun <T : Any, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: () -> R
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java)
|
||||
|
||||
inline fun <T : Any, A, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T , A, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A) -> R,
|
||||
arg0: A
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0)
|
||||
|
||||
inline fun <T : Any, A, B, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B) -> R,
|
||||
arg0: A,
|
||||
arg1: B
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1)
|
||||
|
||||
inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C) -> R,
|
||||
arg0: A,
|
||||
@ -348,7 +345,7 @@ inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
arg2: C
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2)
|
||||
|
||||
inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C, D) -> R,
|
||||
arg0: A,
|
||||
@ -357,7 +354,7 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow
|
||||
arg3: D
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||
|
||||
inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C, D, E) -> R,
|
||||
arg0: A,
|
||||
@ -367,7 +364,7 @@ inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startF
|
||||
arg4: E
|
||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
|
||||
|
||||
inline fun <T : Any, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
inline fun <T, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
flowConstructor: (A, B, C, D, E, F) -> R,
|
||||
arg0: A,
|
||||
@ -382,20 +379,20 @@ inline fun <T : Any, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.sta
|
||||
* Same again, except this time with progress-tracking enabled.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: () -> R
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A) -> R,
|
||||
arg0: A
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B) -> R,
|
||||
arg0: A,
|
||||
@ -403,7 +400,7 @@ inline fun <T : Any, A, B, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlo
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C) -> R,
|
||||
arg0: A,
|
||||
@ -412,7 +409,7 @@ inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startTracked
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C, D) -> R,
|
||||
arg0: A,
|
||||
@ -422,7 +419,7 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startTrac
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C, D, E) -> R,
|
||||
arg0: A,
|
||||
@ -433,7 +430,7 @@ inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startT
|
||||
): FlowProgressHandle<T> = startTrackedFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
|
||||
|
||||
@Suppress("unused")
|
||||
inline fun <T : Any, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
inline fun <T, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(
|
||||
@Suppress("unused_parameter")
|
||||
flowConstructor: (A, B, C, D, E, F) -> R,
|
||||
arg0: A,
|
||||
|
@ -6,7 +6,7 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.locality
|
||||
|
||||
/**
|
||||
* Information for an advertised service including the service specific identity information.
|
||||
@ -21,11 +21,13 @@ data class ServiceEntry(val info: ServiceInfo, val identity: PartyAndCertificate
|
||||
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
|
||||
@CordaSerializable
|
||||
data class NodeInfo(val addresses: List<NetworkHostAndPort>,
|
||||
val legalIdentityAndCert: PartyAndCertificate, //TODO This field will be removed in future PR which gets rid of services.
|
||||
val legalIdentitiesAndCerts: NonEmptySet<PartyAndCertificate>,
|
||||
// TODO After removing of services these two fields will be merged together and made NonEmptySet.
|
||||
val legalIdentityAndCert: PartyAndCertificate,
|
||||
val legalIdentitiesAndCerts: Set<PartyAndCertificate>,
|
||||
val platformVersion: Int,
|
||||
val advertisedServices: List<ServiceEntry> = emptyList(),
|
||||
val worldMapLocation: WorldMapLocation? = null) {
|
||||
val serial: Long
|
||||
) {
|
||||
init {
|
||||
require(advertisedServices.none { it.identity == legalIdentityAndCert }) {
|
||||
"Service identities must be different from node legal identity"
|
||||
@ -37,4 +39,12 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
|
||||
fun serviceIdentities(type: ServiceType): List<Party> {
|
||||
return advertisedServices.mapNotNull { if (it.info.type.isSubTypeOf(type)) it.identity.party else null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses node's owner X500 name to infer the node's location. Used in Explorer in map view.
|
||||
*/
|
||||
fun getWorldMapLocation(): WorldMapLocation? {
|
||||
val nodeOwnerLocation = legalIdentity.name.locality
|
||||
return nodeOwnerLocation.let { CityDatabase[it] }
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
@ -46,6 +47,7 @@ interface ServiceHub : ServicesForResolution {
|
||||
val vaultService: VaultService
|
||||
val vaultQueryService: VaultQueryService
|
||||
val keyManagementService: KeyManagementService
|
||||
val contractUpgradeService: ContractUpgradeService
|
||||
|
||||
/**
|
||||
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
|
||||
@ -170,7 +172,7 @@ interface ServiceHub : ServicesForResolution {
|
||||
|
||||
/**
|
||||
* Helper method to construct an initial partially signed transaction from a TransactionBuilder
|
||||
* using the default identity key contained in the node. The legal Indentity key is used to sign.
|
||||
* using the default identity key contained in the node. The legal identity key is used to sign.
|
||||
* @param builder The TransactionBuilder to seal with the node's signature.
|
||||
* Any existing signatures on the builder will be preserved.
|
||||
* @return Returns a SignedTransaction with the new node signature attached.
|
||||
@ -202,7 +204,9 @@ interface ServiceHub : ServicesForResolution {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create an additional signature for an existing (partially) [SignedTransaction].
|
||||
* Helper method to create an additional signature for an existing (partially) [SignedTransaction]. Additional
|
||||
* [SignatureMetadata], including the
|
||||
* platform version used during signing and the cryptographic signature scheme use, is added to the signature.
|
||||
* @param signedTransaction The [SignedTransaction] to which the signature will apply.
|
||||
* @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
|
||||
* If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
|
||||
@ -213,10 +217,12 @@ interface ServiceHub : ServicesForResolution {
|
||||
createSignature(signedTransaction, publicKey, SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(publicKey).schemeNumberID))
|
||||
|
||||
/**
|
||||
* Helper method to create an additional signature for an existing (partially) [SignedTransaction]
|
||||
* using the default identity signing key of the node. The legal identity key is used to sign.
|
||||
* Helper method to create a signature for an existing (partially) [SignedTransaction]
|
||||
* using the default identity signing key of the node. The legal identity key is used to sign. Additional
|
||||
* [SignatureMetadata], including the
|
||||
* platform version used during signing and the cryptographic signature scheme use, is added to the signature.
|
||||
* @param signedTransaction The SignedTransaction to which the signature will apply.
|
||||
* @return The DigitalSignature.WithKey generated by signing with the internally held identity PrivateKey.
|
||||
* @return the TransactionSignature generated by signing with the internally held identity PrivateKey.
|
||||
*/
|
||||
fun createSignature(signedTransaction: SignedTransaction): TransactionSignature {
|
||||
return createSignature(signedTransaction, legalIdentityKey)
|
||||
@ -242,6 +248,36 @@ interface ServiceHub : ServicesForResolution {
|
||||
*/
|
||||
fun addSignature(signedTransaction: SignedTransaction): SignedTransaction = addSignature(signedTransaction, legalIdentityKey)
|
||||
|
||||
// Helper method to create a signature for a FilteredTransaction.
|
||||
private fun createSignature(filteredTransaction: FilteredTransaction, publicKey: PublicKey, signatureMetadata: SignatureMetadata): TransactionSignature {
|
||||
val signableData = SignableData(filteredTransaction.id, signatureMetadata)
|
||||
return keyManagementService.sign(signableData, publicKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a signature for a FilteredTransaction. Additional [SignatureMetadata], including the
|
||||
* platform version used during signing and the cryptographic signature scheme use, is added to the signature.
|
||||
* @param filteredTransaction the [FilteredTransaction] to which the signature will apply.
|
||||
* @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
|
||||
* If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
|
||||
* for signing.
|
||||
* @return The [TransactionSignature] generated by signing with the internally held [java.security.PrivateKey].
|
||||
*/
|
||||
fun createSignature(filteredTransaction: FilteredTransaction, publicKey: PublicKey) =
|
||||
createSignature(filteredTransaction, publicKey, SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(publicKey).schemeNumberID))
|
||||
|
||||
/**
|
||||
* Helper method to create a signature for a FilteredTransaction
|
||||
* using the default identity signing key of the node. The legal identity key is used to sign. Additional
|
||||
* [SignatureMetadata], including the platform version used during signing and the cryptographic signature scheme use,
|
||||
* is added to the signature.
|
||||
* @param filteredTransaction the FilteredTransaction to which the signature will apply.
|
||||
* @return the [TransactionSignature] generated by signing with the internally held identity [java.security.PrivateKey].
|
||||
*/
|
||||
fun createSignature(filteredTransaction: FilteredTransaction): TransactionSignature {
|
||||
return createSignature(filteredTransaction, legalIdentityKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes a JDBC connection (session) object using the currently configured database.
|
||||
* Applications can use this to execute arbitrary SQL queries (native, direct, prepared, callable)
|
||||
|
@ -0,0 +1,22 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.UpgradedContract
|
||||
import net.corda.core.flows.ContractUpgradeFlow
|
||||
|
||||
/**
|
||||
* The [ContractUpgradeService] is responsible for securely upgrading contract state objects according to
|
||||
* a specified and mutually agreed (amongst participants) contract version.
|
||||
* See also [ContractUpgradeFlow] to understand the workflow associated with contract upgrades.
|
||||
*/
|
||||
interface ContractUpgradeService {
|
||||
|
||||
/** Get contracts we would be willing to upgrade the suggested contract to. */
|
||||
fun getAuthorisedContractUpgrade(ref: StateRef): String?
|
||||
|
||||
/** Store authorised state ref and associated UpgradeContract class */
|
||||
fun storeAuthorisedContractUpgrade(ref: StateRef, upgradedContractClass: Class<out UpgradedContract<*, *>>)
|
||||
|
||||
/** Remove a previously authorised state ref */
|
||||
fun removeAuthorisedContractUpgrade(ref: StateRef)
|
||||
}
|
@ -8,6 +8,7 @@ import net.corda.core.internal.randomOrNull
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import rx.Observable
|
||||
import java.security.PublicKey
|
||||
@ -44,7 +45,7 @@ interface NetworkMapCache {
|
||||
/** Tracks changes to the network map cache */
|
||||
val changed: Observable<MapChange>
|
||||
/** Future to track completion of the NetworkMapService registration. */
|
||||
val mapServiceRegistered: CordaFuture<Void?>
|
||||
val nodeReady: CordaFuture<Void?>
|
||||
|
||||
/**
|
||||
* Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the
|
||||
@ -76,7 +77,10 @@ interface NetworkMapCache {
|
||||
fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo?
|
||||
|
||||
/** Look up the node info for a legal name. */
|
||||
fun getNodeByLegalName(principal: X500Name): NodeInfo? = partyNodes.singleOrNull { it.legalIdentity.name == principal }
|
||||
fun getNodeByLegalName(principal: X500Name): NodeInfo?
|
||||
|
||||
/** Look up the node info for a host and port. */
|
||||
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
|
||||
|
||||
/**
|
||||
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
|
||||
@ -144,4 +148,9 @@ interface NetworkMapCache {
|
||||
"Your options are: ${notaryNodes.map { "\"${it.notaryIdentity.name}\"" }.joinToString()}.")
|
||||
return notary.advertisedServices.any { it.info.type.isValidatingNotary() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all network map data from local node cache.
|
||||
*/
|
||||
fun clearNetworkMapCache()
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class ServiceType private constructor(val id: String) {
|
||||
val regulator: ServiceType = corda.getSubType("regulator")
|
||||
val networkMap: ServiceType = corda.getSubType("network_map")
|
||||
|
||||
@JvmStatic
|
||||
fun getServiceType(namespace: String, typeId: String): ServiceType {
|
||||
require(!namespace.startsWith("corda")) { "Corda namespace is protected" }
|
||||
return baseWithSubType(namespace, typeId)
|
||||
|
@ -173,17 +173,6 @@ interface VaultService {
|
||||
*/
|
||||
val updatesPublisher: PublishSubject<Vault.Update<ContractState>>
|
||||
|
||||
/**
|
||||
* Possibly update the vault by marking as spent states that these transactions consume, and adding any relevant
|
||||
* new states that they create. You should only insert transactions that have been successfully verified here!
|
||||
*
|
||||
* TODO: Consider if there's a good way to enforce the must-be-verified requirement in the type system.
|
||||
*/
|
||||
fun notifyAll(txns: Iterable<CoreTransaction>)
|
||||
|
||||
/** Same as notifyAll but with a single transaction. */
|
||||
fun notify(tx: CoreTransaction) = notifyAll(listOf(tx))
|
||||
|
||||
/**
|
||||
* Provide a [CordaFuture] for when a [StateRef] is consumed, which can be very useful in building tests.
|
||||
*/
|
||||
@ -191,24 +180,6 @@ interface VaultService {
|
||||
return updates.filter { it.consumed.any { it.ref == ref } }.toFuture()
|
||||
}
|
||||
|
||||
/** Get contracts we would be willing to upgrade the suggested contract to. */
|
||||
// TODO: We need a better place to put business logic functions
|
||||
fun getAuthorisedContractUpgrade(ref: StateRef): Class<out UpgradedContract<*, *>>?
|
||||
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
|
||||
* Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
|
||||
* This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
|
||||
*/
|
||||
fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>)
|
||||
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will remove the upgrade authorisation from the vault.
|
||||
*/
|
||||
fun deauthoriseContractUpgrade(stateAndRef: StateAndRef<*>)
|
||||
|
||||
/**
|
||||
* Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id
|
||||
* Multiple notes may be attached to the same [LedgerTransaction].
|
||||
|
@ -4,7 +4,6 @@ import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.FungibleAsset
|
||||
import net.corda.core.contracts.OwnableState
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import java.util.*
|
||||
import javax.persistence.*
|
||||
|
125
core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
Normal file
125
core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
Normal file
@ -0,0 +1,125 @@
|
||||
package net.corda.core.schemas
|
||||
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceEntry
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import java.io.Serializable
|
||||
import javax.persistence.*
|
||||
|
||||
object NodeInfoSchema
|
||||
|
||||
object NodeInfoSchemaV1 : MappedSchema(
|
||||
schemaFamily = NodeInfoSchema.javaClass,
|
||||
version = 1,
|
||||
mappedTypes = listOf(PersistentNodeInfo::class.java, DBPartyAndCertificate::class.java, DBHostAndPort::class.java)
|
||||
) {
|
||||
@Entity
|
||||
@Table(name = "node_infos")
|
||||
class PersistentNodeInfo(
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@Column(name = "node_info_id")
|
||||
var id: Int,
|
||||
|
||||
@Column(name = "addresses")
|
||||
@OneToMany(cascade = arrayOf(CascadeType.ALL), orphanRemoval = true)
|
||||
val addresses: List<NodeInfoSchemaV1.DBHostAndPort>,
|
||||
|
||||
@Column(name = "legal_identities_certs")
|
||||
@ManyToMany(cascade = arrayOf(CascadeType.ALL))
|
||||
@JoinTable(name = "link_nodeinfo_party",
|
||||
joinColumns = arrayOf(JoinColumn(name="node_info_id")),
|
||||
inverseJoinColumns = arrayOf(JoinColumn(name="party_name")))
|
||||
val legalIdentitiesAndCerts: Set<DBPartyAndCertificate>,
|
||||
|
||||
@Column(name = "platform_version")
|
||||
val platformVersion: Int,
|
||||
|
||||
@Column(name = "advertised_services")
|
||||
@ElementCollection
|
||||
var advertisedServices: List<DBServiceEntry> = emptyList(),
|
||||
|
||||
/**
|
||||
* serial is an increasing value which represents the version of [NodeInfo].
|
||||
* Not expected to be sequential, but later versions of the registration must have higher values
|
||||
* Similar to the serial number on DNS records.
|
||||
*/
|
||||
@Column(name = "serial")
|
||||
val serial: Long
|
||||
) {
|
||||
fun toNodeInfo(): NodeInfo {
|
||||
return NodeInfo(
|
||||
this.addresses.map { it.toHostAndPort() },
|
||||
this.legalIdentitiesAndCerts.filter { it.isMain }.single().toLegalIdentityAndCert(), // TODO Workaround, it will be changed after PR with services removal.
|
||||
this.legalIdentitiesAndCerts.filter { !it.isMain }.map { it.toLegalIdentityAndCert() }.toSet(),
|
||||
this.platformVersion,
|
||||
this.advertisedServices.map {
|
||||
it.serviceEntry?.deserialize<ServiceEntry>() ?: throw IllegalStateException("Service entry shouldn't be null")
|
||||
},
|
||||
this.serial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Embeddable
|
||||
data class PKHostAndPort(
|
||||
val host: String? = null,
|
||||
val port: Int? = null
|
||||
) : Serializable
|
||||
|
||||
@Entity
|
||||
data class DBHostAndPort(
|
||||
@EmbeddedId
|
||||
private val pk: PKHostAndPort
|
||||
) {
|
||||
companion object {
|
||||
fun fromHostAndPort(hostAndPort: NetworkHostAndPort) = DBHostAndPort(
|
||||
PKHostAndPort(hostAndPort.host, hostAndPort.port)
|
||||
)
|
||||
}
|
||||
fun toHostAndPort(): NetworkHostAndPort {
|
||||
return NetworkHostAndPort(this.pk.host!!, this.pk.port!!)
|
||||
}
|
||||
}
|
||||
|
||||
@Embeddable // TODO To be removed with services.
|
||||
data class DBServiceEntry(
|
||||
@Column(length = 65535)
|
||||
val serviceEntry: ByteArray? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* PartyAndCertificate entity (to be replaced by referencing final Identity Schema).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "node_info_party_cert")
|
||||
data class DBPartyAndCertificate(
|
||||
@Id
|
||||
@Column(name = "owning_key", length = 65535, nullable = false)
|
||||
val owningKey: String,
|
||||
|
||||
//@Id // TODO Do we assume that names are unique? Note: We can't have it as Id, because our toString on X500 is inconsistent.
|
||||
@Column(name = "party_name", nullable = false)
|
||||
val name: String,
|
||||
|
||||
@Column(name = "party_cert_binary")
|
||||
@Lob
|
||||
val partyCertBinary: ByteArray,
|
||||
|
||||
val isMain: Boolean,
|
||||
|
||||
@ManyToMany(mappedBy = "legalIdentitiesAndCerts", cascade = arrayOf(CascadeType.ALL)) // ManyToMany because of distributed services.
|
||||
private val persistentNodeInfos: Set<PersistentNodeInfo> = emptySet()
|
||||
) {
|
||||
constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false)
|
||||
: this(partyAndCert.party.owningKey.toBase58String(), partyAndCert.party.name.toString(), partyAndCert.serialize().bytes, isMain)
|
||||
|
||||
fun toLegalIdentityAndCert(): PartyAndCertificate {
|
||||
return partyCertBinary.deserialize<PartyAndCertificate>()
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,6 @@ package net.corda.core.serialization
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.WriteOnceProperty
|
||||
import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT
|
||||
import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.sequence
|
||||
@ -13,7 +11,7 @@ import net.corda.core.utilities.sequence
|
||||
* An abstraction for serializing and deserializing objects, with support for versioning of the wire format via
|
||||
* a header / prefix in the bytes.
|
||||
*/
|
||||
interface SerializationFactory {
|
||||
abstract class SerializationFactory {
|
||||
/**
|
||||
* Deserialize the bytes in to an object, using the prefixed bytes to determine the format.
|
||||
*
|
||||
@ -21,7 +19,7 @@ interface SerializationFactory {
|
||||
* @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown.
|
||||
* @param context A context that configures various parameters to deserialization.
|
||||
*/
|
||||
fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T
|
||||
abstract fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T
|
||||
|
||||
/**
|
||||
* Serialize an object to bytes using the preferred serialization format version from the context.
|
||||
@ -29,7 +27,63 @@ interface SerializationFactory {
|
||||
* @param obj The object to be serialized.
|
||||
* @param context A context that configures various parameters to serialization, including the serialization format version.
|
||||
*/
|
||||
fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T>
|
||||
abstract fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T>
|
||||
|
||||
/**
|
||||
* If there is a need to nest serialization/deserialization with a modified context during serialization or deserialization,
|
||||
* this will return the current context used to start serialization/deserialization.
|
||||
*/
|
||||
val currentContext: SerializationContext? get() = _currentContext.get()
|
||||
|
||||
/**
|
||||
* A context to use as a default if you do not require a specially configured context. It will be the current context
|
||||
* if the use is somehow nested (see [currentContext]).
|
||||
*/
|
||||
val defaultContext: SerializationContext get() = currentContext ?: SerializationDefaults.P2P_CONTEXT
|
||||
|
||||
private val _currentContext = ThreadLocal<SerializationContext?>()
|
||||
|
||||
/**
|
||||
* Change the current context inside the block to that supplied.
|
||||
*/
|
||||
fun <T> withCurrentContext(context: SerializationContext?, block: () -> T): T {
|
||||
val priorContext = _currentContext.get()
|
||||
if (context != null) _currentContext.set(context)
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
if (context != null) _currentContext.set(priorContext)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow subclasses to temporarily mark themselves as the current factory for the current thread during serialization/deserialization.
|
||||
* Will restore the prior context on exiting the block.
|
||||
*/
|
||||
protected fun <T> asCurrent(block: SerializationFactory.() -> T): T {
|
||||
val priorContext = _currentFactory.get()
|
||||
_currentFactory.set(this)
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
_currentFactory.set(priorContext)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val _currentFactory = ThreadLocal<SerializationFactory?>()
|
||||
|
||||
/**
|
||||
* A default factory for serialization/deserialization, taking into account the [currentFactory] if set.
|
||||
*/
|
||||
val defaultFactory: SerializationFactory get() = currentFactory ?: SerializationDefaults.SERIALIZATION_FACTORY
|
||||
|
||||
/**
|
||||
* If there is a need to nest serialization/deserialization with a modified context during serialization or deserialization,
|
||||
* this will return the current factory used to start serialization/deserialization.
|
||||
*/
|
||||
val currentFactory: SerializationFactory? get() = _currentFactory.get()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,6 +130,12 @@ interface SerializationContext {
|
||||
*/
|
||||
fun withClassLoader(classLoader: ClassLoader): SerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the appropriate class loader constructed from the passed attachment identifiers.
|
||||
* (Requires the attachment storage to have been enabled).
|
||||
*/
|
||||
fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): SerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the given class specifically whitelisted.
|
||||
*/
|
||||
@ -107,26 +167,26 @@ object SerializationDefaults {
|
||||
/**
|
||||
* Convenience extension method for deserializing a ByteSequence, utilising the defaults.
|
||||
*/
|
||||
inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T {
|
||||
inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T {
|
||||
return serializationFactory.deserialize(this, T::class.java, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
|
||||
*/
|
||||
inline fun <reified T : Any> SerializedBytes<T>.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T {
|
||||
inline fun <reified T : Any> SerializedBytes<T>.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T {
|
||||
return serializationFactory.deserialize(this, T::class.java, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience extension method for deserializing a ByteArray, utilising the defaults.
|
||||
*/
|
||||
inline fun <reified T : Any> ByteArray.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T = this.sequence().deserialize(serializationFactory, context)
|
||||
inline fun <reified T : Any> ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.sequence().deserialize(serializationFactory, context)
|
||||
|
||||
/**
|
||||
* Convenience extension method for serializing an object of type T, utilising the defaults.
|
||||
*/
|
||||
fun <T : Any> T.serialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): SerializedBytes<T> {
|
||||
fun <T : Any> T.serialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): SerializedBytes<T> {
|
||||
return serializationFactory.serialize(this, context)
|
||||
}
|
||||
|
||||
@ -142,4 +202,4 @@ class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
|
||||
interface ClassWhitelist {
|
||||
fun hasListed(type: Class<*>): Boolean
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
interface SerializationCustomization {
|
||||
fun addToWhitelist(type: Class<*>)
|
||||
fun addToWhitelist(vararg types: Class<*>)
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,8 @@ abstract class BaseTransaction : NamedByHash {
|
||||
}
|
||||
|
||||
private fun checkNotarySetIfInputsPresent() {
|
||||
if (notary == null) {
|
||||
check(inputs.isEmpty()) { "The notary must be specified explicitly for any transaction that has inputs" }
|
||||
if (inputs.isNotEmpty()) {
|
||||
check(notary != null) { "The notary must be specified explicitly for any transaction that has inputs" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import java.util.function.Predicate
|
||||
* - Downloading and locally storing all the dependencies of the transaction.
|
||||
* - Resolving the input states and loading them into memory.
|
||||
* - Doing some basic key lookups on the [Command]s to see if any keys are from a recognised party, thus converting the
|
||||
* [Command] objects into [AuthenticatedObject].
|
||||
* [Command] objects into [CommandWithParties].
|
||||
* - Deserialising the output states.
|
||||
*
|
||||
* All the above refer to inputs using a (txhash, output index) pair.
|
||||
@ -28,7 +28,7 @@ data class LedgerTransaction(
|
||||
override val inputs: List<StateAndRef<ContractState>>,
|
||||
override val outputs: List<TransactionState<ContractState>>,
|
||||
/** Arbitrary data passed to the program of each input state. */
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
val commands: List<CommandWithParties<CommandData>>,
|
||||
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||
val attachments: List<Attachment>,
|
||||
/** The hash of the original serialised WireTransaction. */
|
||||
|
@ -4,7 +4,7 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.serialize
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.function.Predicate
|
||||
@ -22,12 +22,12 @@ fun <T : Any> serializedHash(x: T, privacySalt: PrivacySalt?, index: Int): Secur
|
||||
|
||||
fun <T : Any> serializedHash(x: T, nonce: SecureHash): SecureHash {
|
||||
return if (x !is PrivacySalt) // PrivacySalt is not required to have an accompanied nonce.
|
||||
(x.serialize(context = P2P_CONTEXT.withoutReferences()).bytes + nonce.bytes).sha256()
|
||||
(x.serialize(context = SerializationFactory.defaultFactory.defaultContext.withoutReferences()).bytes + nonce.bytes).sha256()
|
||||
else
|
||||
serializedHash(x)
|
||||
}
|
||||
|
||||
fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = P2P_CONTEXT.withoutReferences()).bytes.sha256()
|
||||
fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = SerializationFactory.defaultFactory.defaultContext.withoutReferences()).bytes.sha256()
|
||||
|
||||
/** The nonce is computed as Hash(privacySalt || index). */
|
||||
fun computeNonce(privacySalt: PrivacySalt, index: Int) = (privacySalt.bytes + ByteBuffer.allocate(4).putInt(index).array()).sha256()
|
||||
@ -139,13 +139,13 @@ class FilteredLeaves(
|
||||
|
||||
/**
|
||||
* Class representing merkleized filtered transaction.
|
||||
* @param rootHash Merkle tree root hash.
|
||||
* @param id Merkle tree root hash.
|
||||
* @param filteredLeaves Leaves included in a filtered transaction.
|
||||
* @param partialMerkleTree Merkle branch needed to verify filteredLeaves.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class FilteredTransaction private constructor(
|
||||
val rootHash: SecureHash,
|
||||
val id: SecureHash,
|
||||
val filteredLeaves: FilteredLeaves,
|
||||
val partialMerkleTree: PartialMerkleTree
|
||||
) {
|
||||
@ -159,21 +159,84 @@ class FilteredTransaction private constructor(
|
||||
fun buildMerkleTransaction(wtx: WireTransaction,
|
||||
filtering: Predicate<Any>
|
||||
): FilteredTransaction {
|
||||
val filteredLeaves = wtx.filterWithFun(filtering)
|
||||
val filteredLeaves = filterWithFun(wtx, filtering)
|
||||
val merkleTree = wtx.merkleTree
|
||||
val pmt = PartialMerkleTree.build(merkleTree, filteredLeaves.availableComponentHashes)
|
||||
return FilteredTransaction(merkleTree.hash, filteredLeaves, pmt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Construction of partial transaction from WireTransaction based on filtering.
|
||||
* Note that list of nonces to be sent is updated on the fly, based on the index of the filtered tx component.
|
||||
* @param filtering filtering over the whole WireTransaction
|
||||
* @returns FilteredLeaves used in PartialMerkleTree calculation and verification.
|
||||
*/
|
||||
private fun filterWithFun(wtx: WireTransaction, filtering: Predicate<Any>): FilteredLeaves {
|
||||
val nonces: MutableList<SecureHash> = mutableListOf()
|
||||
val offsets = indexOffsets(wtx)
|
||||
fun notNullFalseAndNoncesUpdate(elem: Any?, index: Int): Any? {
|
||||
return if (elem == null || !filtering.test(elem)) {
|
||||
null
|
||||
} else {
|
||||
nonces.add(computeNonce(wtx.privacySalt, index))
|
||||
elem
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> filterAndNoncesUpdate(t: T, index: Int): Boolean {
|
||||
return if (filtering.test(t)) {
|
||||
nonces.add(computeNonce(wtx.privacySalt, index))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We should have a warning (require) if all leaves (excluding salt) are visible after filtering.
|
||||
// Consider the above after refactoring FilteredTransaction to implement TraversableTransaction,
|
||||
// so that a WireTransaction can be used when required to send a full tx (e.g. RatesFixFlow in Oracles).
|
||||
return FilteredLeaves(
|
||||
wtx.inputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index) },
|
||||
wtx.attachments.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[0]) },
|
||||
wtx.outputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[1]) },
|
||||
wtx.commands.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[2]) },
|
||||
notNullFalseAndNoncesUpdate(wtx.notary, offsets[3]) as Party?,
|
||||
notNullFalseAndNoncesUpdate(wtx.timeWindow, offsets[4]) as TimeWindow?,
|
||||
nonces
|
||||
)
|
||||
}
|
||||
|
||||
// We use index offsets, to get the actual leaf-index per transaction component required for nonce computation.
|
||||
private fun indexOffsets(wtx: WireTransaction): List<Int> {
|
||||
// There is no need to add an index offset for inputs, because they are the first components in the
|
||||
// transaction format and it is always zero. Thus, offsets[0] corresponds to attachments,
|
||||
// offsets[1] to outputs, offsets[2] to commands and so on.
|
||||
val offsets = mutableListOf(wtx.inputs.size, wtx.inputs.size + wtx.attachments.size)
|
||||
offsets.add(offsets.last() + wtx.outputs.size)
|
||||
offsets.add(offsets.last() + wtx.commands.size)
|
||||
if (wtx.notary != null) {
|
||||
offsets.add(offsets.last() + 1)
|
||||
} else {
|
||||
offsets.add(offsets.last())
|
||||
}
|
||||
if (wtx.timeWindow != null) {
|
||||
offsets.add(offsets.last() + 1)
|
||||
} else {
|
||||
offsets.add(offsets.last())
|
||||
}
|
||||
// No need to add offset for privacySalt as it doesn't require a nonce.
|
||||
return offsets
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs verification of Partial Merkle Branch against [rootHash].
|
||||
* Runs verification of partial Merkle branch against [id].
|
||||
*/
|
||||
@Throws(MerkleTreeException::class)
|
||||
fun verify(): Boolean {
|
||||
val hashes: List<SecureHash> = filteredLeaves.availableComponentHashes
|
||||
if (hashes.isEmpty())
|
||||
throw MerkleTreeException("Transaction without included leaves.")
|
||||
return partialMerkleTree.verify(rootHash, hashes)
|
||||
return partialMerkleTree.verify(id, hashes)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package net.corda.core.transactions
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import java.security.PublicKey
|
||||
|
@ -14,6 +14,7 @@ import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.security.SignatureException
|
||||
import java.util.*
|
||||
import java.util.function.Predicate
|
||||
|
||||
/**
|
||||
* SignedTransaction wraps a serialized WireTransaction. It contains one or more signatures, each one for
|
||||
@ -29,6 +30,7 @@ import java.util.*
|
||||
* sign.
|
||||
*/
|
||||
// DOCSTART 1
|
||||
@CordaSerializable
|
||||
data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
override val sigs: List<TransactionSignature>
|
||||
) : TransactionWithSignatures {
|
||||
@ -50,12 +52,18 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
/** The id of the contained [WireTransaction]. */
|
||||
override val id: SecureHash get() = transaction.id
|
||||
|
||||
/** Returns the contained [WireTransaction], or throws if this is a notary change transaction */
|
||||
/** Returns the contained [WireTransaction], or throws if this is a notary change transaction. */
|
||||
val tx: WireTransaction get() = transaction as WireTransaction
|
||||
|
||||
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction */
|
||||
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
||||
val notaryChangeTx: NotaryChangeWireTransaction get() = transaction as NotaryChangeWireTransaction
|
||||
|
||||
/**
|
||||
* Helper function to directly build a [FilteredTransaction] using provided filtering functions,
|
||||
* without first accessing the [WireTransaction] [tx].
|
||||
*/
|
||||
fun buildFilteredTransaction(filtering: Predicate<Any>) = tx.buildFilteredTransaction(filtering)
|
||||
|
||||
/** Helper to access the inputs of the contained transaction */
|
||||
val inputs: List<StateRef> get() = transaction.inputs
|
||||
/** Helper to access the notary of the contained transaction */
|
||||
|
@ -33,8 +33,9 @@ data class WireTransaction(
|
||||
) : CoreTransaction(), TraversableTransaction {
|
||||
init {
|
||||
checkBaseInvariants()
|
||||
check(inputs.isNotEmpty() || outputs.isNotEmpty()) { "A transaction must contain at least one input or output state" }
|
||||
check(commands.isNotEmpty()) { "A transaction must contain at least one command" }
|
||||
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
||||
check(availableComponents.isNotEmpty()) { "A WireTransaction cannot be empty" }
|
||||
}
|
||||
|
||||
/** The transaction id is represented by the root hash of Merkle tree over the transaction components. */
|
||||
@ -83,7 +84,7 @@ data class WireTransaction(
|
||||
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
|
||||
val authenticatedArgs = commands.map {
|
||||
val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) }
|
||||
AuthenticatedObject(it.signers, parties, it.value)
|
||||
CommandWithParties(it.signers, parties, it.value)
|
||||
}
|
||||
// Open attachments specified in this transaction. If we haven't downloaded them, we fail.
|
||||
val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
|
||||
@ -105,69 +106,6 @@ data class WireTransaction(
|
||||
*/
|
||||
val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(availableComponentHashes) }
|
||||
|
||||
/**
|
||||
* Construction of partial transaction from WireTransaction based on filtering.
|
||||
* Note that list of nonces to be sent is updated on the fly, based on the index of the filtered tx component.
|
||||
* @param filtering filtering over the whole WireTransaction
|
||||
* @returns FilteredLeaves used in PartialMerkleTree calculation and verification.
|
||||
*/
|
||||
fun filterWithFun(filtering: Predicate<Any>): FilteredLeaves {
|
||||
val nonces: MutableList<SecureHash> = mutableListOf()
|
||||
val offsets = indexOffsets()
|
||||
fun notNullFalseAndNoncesUpdate(elem: Any?, index: Int): Any? {
|
||||
return if (elem == null || !filtering.test(elem)) {
|
||||
null
|
||||
} else {
|
||||
nonces.add(computeNonce(privacySalt, index))
|
||||
elem
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> filterAndNoncesUpdate(t: T, index: Int): Boolean {
|
||||
return if (filtering.test(t)) {
|
||||
nonces.add(computeNonce(privacySalt, index))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We should have a warning (require) if all leaves (excluding salt) are visible after filtering.
|
||||
// Consider the above after refactoring FilteredTransaction to implement TraversableTransaction,
|
||||
// so that a WireTransaction can be used when required to send a full tx (e.g. RatesFixFlow in Oracles).
|
||||
return FilteredLeaves(
|
||||
inputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index) },
|
||||
attachments.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[0]) },
|
||||
outputs.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[1]) },
|
||||
commands.filterIndexed { index, it -> filterAndNoncesUpdate(it, index + offsets[2]) },
|
||||
notNullFalseAndNoncesUpdate(notary, offsets[3]) as Party?,
|
||||
notNullFalseAndNoncesUpdate(timeWindow, offsets[4]) as TimeWindow?,
|
||||
nonces
|
||||
)
|
||||
}
|
||||
|
||||
// We use index offsets, to get the actual leaf-index per transaction component required for nonce computation.
|
||||
private fun indexOffsets(): List<Int> {
|
||||
// There is no need to add an index offset for inputs, because they are the first components in the
|
||||
// transaction format and it is always zero. Thus, offsets[0] corresponds to attachments,
|
||||
// offsets[1] to outputs, offsets[2] to commands and so on.
|
||||
val offsets = mutableListOf(inputs.size, inputs.size + attachments.size)
|
||||
offsets.add(offsets.last() + outputs.size)
|
||||
offsets.add(offsets.last() + commands.size)
|
||||
if (notary != null) {
|
||||
offsets.add(offsets.last() + 1)
|
||||
} else {
|
||||
offsets.add(offsets.last())
|
||||
}
|
||||
if (timeWindow != null) {
|
||||
offsets.add(offsets.last() + 1)
|
||||
} else {
|
||||
offsets.add(offsets.last())
|
||||
}
|
||||
// No need to add offset for privacySalt as it doesn't require a nonce.
|
||||
return offsets
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
|
||||
*
|
||||
|
253
core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt
Normal file
253
core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt
Normal file
@ -0,0 +1,253 @@
|
||||
package net.corda.core.utilities
|
||||
|
||||
val countryCodes = hashSetOf(
|
||||
"AF",
|
||||
"AX",
|
||||
"AL",
|
||||
"DZ",
|
||||
"AS",
|
||||
"AD",
|
||||
"AO",
|
||||
"AI",
|
||||
"AQ",
|
||||
"AG",
|
||||
"AR",
|
||||
"AM",
|
||||
"AW",
|
||||
"AU",
|
||||
"AT",
|
||||
"AZ",
|
||||
"BS",
|
||||
"BH",
|
||||
"BD",
|
||||
"BB",
|
||||
"BY",
|
||||
"BE",
|
||||
"BZ",
|
||||
"BJ",
|
||||
"BM",
|
||||
"BT",
|
||||
"BO",
|
||||
"BQ",
|
||||
"BA",
|
||||
"BW",
|
||||
"BV",
|
||||
"BR",
|
||||
"IO",
|
||||
"BN",
|
||||
"BG",
|
||||
"BF",
|
||||
"BI",
|
||||
"KH",
|
||||
"CM",
|
||||
"CA",
|
||||
"CV",
|
||||
"KY",
|
||||
"CF",
|
||||
"TD",
|
||||
"CL",
|
||||
"CN",
|
||||
"CX",
|
||||
"CC",
|
||||
"CO",
|
||||
"KM",
|
||||
"CG",
|
||||
"CD",
|
||||
"CK",
|
||||
"CR",
|
||||
"CI",
|
||||
"HR",
|
||||
"CU",
|
||||
"CW",
|
||||
"CY",
|
||||
"CZ",
|
||||
"DK",
|
||||
"DJ",
|
||||
"DM",
|
||||
"DO",
|
||||
"EC",
|
||||
"EG",
|
||||
"SV",
|
||||
"GQ",
|
||||
"ER",
|
||||
"EE",
|
||||
"ET",
|
||||
"FK",
|
||||
"FO",
|
||||
"FJ",
|
||||
"FI",
|
||||
"FR",
|
||||
"GF",
|
||||
"PF",
|
||||
"TF",
|
||||
"GA",
|
||||
"GM",
|
||||
"GE",
|
||||
"DE",
|
||||
"GH",
|
||||
"GI",
|
||||
"GR",
|
||||
"GL",
|
||||
"GD",
|
||||
"GP",
|
||||
"GU",
|
||||
"GT",
|
||||
"GG",
|
||||
"GN",
|
||||
"GW",
|
||||
"GY",
|
||||
"HT",
|
||||
"HM",
|
||||
"VA",
|
||||
"HN",
|
||||
"HK",
|
||||
"HU",
|
||||
"IS",
|
||||
"IN",
|
||||
"ID",
|
||||
"IR",
|
||||
"IQ",
|
||||
"IE",
|
||||
"IM",
|
||||
"IL",
|
||||
"IT",
|
||||
"JM",
|
||||
"JP",
|
||||
"JE",
|
||||
"JO",
|
||||
"KZ",
|
||||
"KE",
|
||||
"KI",
|
||||
"KP",
|
||||
"KR",
|
||||
"KW",
|
||||
"KG",
|
||||
"LA",
|
||||
"LV",
|
||||
"LB",
|
||||
"LS",
|
||||
"LR",
|
||||
"LY",
|
||||
"LI",
|
||||
"LT",
|
||||
"LU",
|
||||
"MO",
|
||||
"MK",
|
||||
"MG",
|
||||
"MW",
|
||||
"MY",
|
||||
"MV",
|
||||
"ML",
|
||||
"MT",
|
||||
"MH",
|
||||
"MQ",
|
||||
"MR",
|
||||
"MU",
|
||||
"YT",
|
||||
"MX",
|
||||
"FM",
|
||||
"MD",
|
||||
"MC",
|
||||
"MN",
|
||||
"ME",
|
||||
"MS",
|
||||
"MA",
|
||||
"MZ",
|
||||
"MM",
|
||||
"NA",
|
||||
"NR",
|
||||
"NP",
|
||||
"NL",
|
||||
"NC",
|
||||
"NZ",
|
||||
"NI",
|
||||
"NE",
|
||||
"NG",
|
||||
"NU",
|
||||
"NF",
|
||||
"MP",
|
||||
"NO",
|
||||
"OM",
|
||||
"PK",
|
||||
"PW",
|
||||
"PS",
|
||||
"PA",
|
||||
"PG",
|
||||
"PY",
|
||||
"PE",
|
||||
"PH",
|
||||
"PN",
|
||||
"PL",
|
||||
"PT",
|
||||
"PR",
|
||||
"QA",
|
||||
"RE",
|
||||
"RO",
|
||||
"RU",
|
||||
"RW",
|
||||
"BL",
|
||||
"SH",
|
||||
"KN",
|
||||
"LC",
|
||||
"MF",
|
||||
"PM",
|
||||
"VC",
|
||||
"WS",
|
||||
"SM",
|
||||
"ST",
|
||||
"SA",
|
||||
"SN",
|
||||
"RS",
|
||||
"SC",
|
||||
"SL",
|
||||
"SG",
|
||||
"SX",
|
||||
"SK",
|
||||
"SI",
|
||||
"SB",
|
||||
"SO",
|
||||
"ZA",
|
||||
"GS",
|
||||
"SS",
|
||||
"ES",
|
||||
"LK",
|
||||
"SD",
|
||||
"SR",
|
||||
"SJ",
|
||||
"SZ",
|
||||
"SE",
|
||||
"CH",
|
||||
"SY",
|
||||
"TW",
|
||||
"TJ",
|
||||
"TZ",
|
||||
"TH",
|
||||
"TL",
|
||||
"TG",
|
||||
"TK",
|
||||
"TO",
|
||||
"TT",
|
||||
"TN",
|
||||
"TR",
|
||||
"TM",
|
||||
"TC",
|
||||
"TV",
|
||||
"UG",
|
||||
"UA",
|
||||
"AE",
|
||||
"GB",
|
||||
"US",
|
||||
"UM",
|
||||
"UY",
|
||||
"UZ",
|
||||
"VU",
|
||||
"VE",
|
||||
"VN",
|
||||
"VG",
|
||||
"VI",
|
||||
"WF",
|
||||
"EH",
|
||||
"YE",
|
||||
"ZM",
|
||||
"ZW"
|
||||
)
|
@ -1,7 +1,9 @@
|
||||
@file:JvmName("EncodingUtils")
|
||||
|
||||
package net.corda.core.crypto
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.crypto.Base58
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import java.nio.charset.Charset
|
@ -2,8 +2,8 @@
|
||||
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.crypto.commonName
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import java.lang.Character.UnicodeScript.*
|
||||
import java.text.Normalizer
|
||||
import java.util.regex.Pattern
|
||||
@ -22,16 +22,9 @@ import javax.security.auth.x500.X500Principal
|
||||
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
||||
*/
|
||||
fun validateLegalName(normalizedLegalName: String) {
|
||||
legalNameRules.forEach { it.validate(normalizedLegalName) }
|
||||
Rule.legalNameRules.forEach { it.validate(normalizedLegalName) }
|
||||
}
|
||||
|
||||
// TODO: Implement X500 attribute validation once the specification has been finalised.
|
||||
fun validateX500Name(x500Name: X500Name) {
|
||||
validateLegalName(x500Name.commonName)
|
||||
}
|
||||
|
||||
val WHITESPACE = "\\s++".toRegex()
|
||||
|
||||
/**
|
||||
* The normalize function will trim the input string, replace any multiple spaces with a single space,
|
||||
* and normalize the string according to NFKC normalization form.
|
||||
@ -41,82 +34,137 @@ fun normaliseLegalName(legalName: String): String {
|
||||
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
|
||||
}
|
||||
|
||||
private val legalNameRules: List<Rule<String>> = listOf(
|
||||
UnicodeNormalizationRule(),
|
||||
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
||||
WordRule("node", "server"),
|
||||
LengthRule(maxLength = 255),
|
||||
// TODO: Implement confusable character detection if we add more scripts.
|
||||
UnicodeRangeRule(LATIN, COMMON, INHERITED),
|
||||
CapitalLetterRule(),
|
||||
X500NameRule(),
|
||||
MustHaveAtLeastTwoLettersRule()
|
||||
)
|
||||
val WHITESPACE = "\\s++".toRegex()
|
||||
|
||||
private class UnicodeNormalizationRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." }
|
||||
private val mandatoryAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L)
|
||||
private val supportedAttributes = mandatoryAttributes + setOf(BCStyle.CN, BCStyle.ST, BCStyle.OU)
|
||||
|
||||
/**
|
||||
* Validate X500Name according to Corda X500Name specification
|
||||
*
|
||||
* Supported attributes:
|
||||
* - organisation (O) – VARCHAR(127)
|
||||
* - state (ST) – VARCHAR(64) nullable
|
||||
* - locality (L) – VARCHAR(64)
|
||||
* - country (C) – VARCHAR(2) - ISO code of the country in which it is registered
|
||||
* - organizational-unit (OU) – VARCHAR(64) nullable
|
||||
* - common name (CN) – VARCHAR(64)
|
||||
*
|
||||
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
||||
* @see <a href="https://r3-cev.atlassian.net/wiki/spaces/AWG/pages/129206341/Distinguished+name+structure">Design Doc</a>.
|
||||
*/
|
||||
fun validateX500Name(x500Name: X500Name) {
|
||||
val rDNs = x500Name.rdNs.flatMap { it.typesAndValues.toList() }
|
||||
val attributes = rDNs.map { it.type }
|
||||
|
||||
// Duplicate attribute value checks.
|
||||
require(attributes.size == attributes.toSet().size) { "X500Name contain duplicate attribute." }
|
||||
|
||||
// Mandatory attribute checks.
|
||||
require(attributes.containsAll(mandatoryAttributes)) {
|
||||
val missingAttributes = mandatoryAttributes.subtract(attributes).map { BCStyle.INSTANCE.oidToDisplayName(it) }
|
||||
"The following attribute${if (missingAttributes.size > 1) "s are" else " is"} missing from the legal name : $missingAttributes"
|
||||
}
|
||||
|
||||
// Supported attribute checks.
|
||||
require(attributes.subtract(supportedAttributes).isEmpty()) {
|
||||
val unsupportedAttributes = attributes.subtract(supportedAttributes).map { BCStyle.INSTANCE.oidToDisplayName(it) }
|
||||
"The following attribute${if (unsupportedAttributes.size > 1) "s are" else " is"} not supported in Corda :$unsupportedAttributes"
|
||||
}
|
||||
// Legal name checks.
|
||||
validateLegalName(x500Name.organisation)
|
||||
|
||||
// Attribute data width checks.
|
||||
require(x500Name.country.length == 2) { "Invalid country '${x500Name.country}' Country code must be 2 letters ISO code " }
|
||||
require(x500Name.country.toUpperCase() == x500Name.country) { "Country code should be in upper case." }
|
||||
require(countryCodes.contains(x500Name.country)) { "Invalid country code '${x500Name.country}'" }
|
||||
|
||||
require(x500Name.organisation.length < 127) { "Organisation attribute (O) must contain less then 127 characters." }
|
||||
require(x500Name.locality.length < 64) { "Locality attribute (L) must contain less then 64 characters." }
|
||||
|
||||
x500Name.state?.let { require(it.length < 64) { "State attribute (ST) must contain less then 64 characters." } }
|
||||
x500Name.organisationUnit?.let { require(x500Name.organisationUnit!!.length < 64) { "Organisation Unit attribute (OU) must contain less then 64 characters." } }
|
||||
x500Name.commonName?.let { require(x500Name.commonName!!.length < 64) { "Common Name attribute (CN) must contain less then 64 characters." } }
|
||||
}
|
||||
|
||||
private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeScript) : Rule<String> {
|
||||
private val pattern = supportScripts.map { "\\p{Is$it}" }.joinToString(separator = "", prefix = "[", postfix = "]*").let { Pattern.compile(it) }
|
||||
private sealed class Rule<in T> {
|
||||
companion object {
|
||||
val legalNameRules: List<Rule<String>> = listOf(
|
||||
UnicodeNormalizationRule(),
|
||||
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
||||
WordRule("node", "server"),
|
||||
LengthRule(maxLength = 255),
|
||||
// TODO: Implement confusable character detection if we add more scripts.
|
||||
UnicodeRangeRule(LATIN, COMMON, INHERITED),
|
||||
CapitalLetterRule(),
|
||||
X500NameRule(),
|
||||
MustHaveAtLeastTwoLettersRule()
|
||||
)
|
||||
}
|
||||
|
||||
override fun validate(legalName: String) {
|
||||
require(pattern.matcher(legalName).matches()) {
|
||||
val illegalChars = legalName.replace(pattern.toRegex(), "").toSet()
|
||||
if (illegalChars.size > 1) {
|
||||
"Forbidden characters $illegalChars in \"$legalName\"."
|
||||
} else {
|
||||
"Forbidden character $illegalChars in \"$legalName\"."
|
||||
abstract fun validate(legalName: T)
|
||||
|
||||
private class UnicodeNormalizationRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." }
|
||||
}
|
||||
}
|
||||
|
||||
private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeScript) : Rule<String>() {
|
||||
private val pattern = supportScripts.map { "\\p{Is$it}" }.joinToString(separator = "", prefix = "[", postfix = "]*").let { Pattern.compile(it) }
|
||||
|
||||
override fun validate(legalName: String) {
|
||||
require(pattern.matcher(legalName).matches()) {
|
||||
val illegalChars = legalName.replace(pattern.toRegex(), "").toSet()
|
||||
if (illegalChars.size > 1) {
|
||||
"Forbidden characters $illegalChars in \"$legalName\"."
|
||||
} else {
|
||||
"Forbidden character $illegalChars in \"$legalName\"."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CharacterRule(vararg val bannedChars: Char) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
bannedChars.forEach {
|
||||
require(!legalName.contains(it, true)) { "Character not allowed in legal names: $it" }
|
||||
private class CharacterRule(vararg val bannedChars: Char) : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
bannedChars.forEach {
|
||||
require(!legalName.contains(it, true)) { "Character not allowed in legal names: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WordRule(vararg val bannedWords: String) : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
bannedWords.forEach {
|
||||
require(!legalName.contains(it, ignoreCase = true)) { "Word not allowed in legal names: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LengthRule(val maxLength: Int) : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
|
||||
}
|
||||
}
|
||||
|
||||
private class CapitalLetterRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
val capitalizedLegalName = legalName.capitalize()
|
||||
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
|
||||
}
|
||||
}
|
||||
|
||||
private class X500NameRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
|
||||
X500Principal("CN=$legalName")
|
||||
}
|
||||
}
|
||||
|
||||
private class MustHaveAtLeastTwoLettersRule : Rule<String>() {
|
||||
override fun validate(legalName: String) {
|
||||
// Try to exclude names like "/", "£", "X" etc.
|
||||
require(legalName.count { it.isLetter() } >= 2) { "Illegal input legal name '$legalName'. Legal name must have at least two letters" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WordRule(vararg val bannedWords: String) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
bannedWords.forEach {
|
||||
require(!legalName.contains(it, ignoreCase = true)) { "Word not allowed in legal names: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LengthRule(val maxLength: Int) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
|
||||
}
|
||||
}
|
||||
|
||||
private class CapitalLetterRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
val capitalizedLegalName = legalName.capitalize()
|
||||
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
|
||||
}
|
||||
}
|
||||
|
||||
private class X500NameRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
|
||||
X500Principal("CN=$legalName")
|
||||
}
|
||||
}
|
||||
|
||||
private class MustHaveAtLeastTwoLettersRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
// Try to exclude names like "/", "£", "X" etc.
|
||||
require(legalName.count { it.isLetter() } >= 2) { "Illegal input legal name '$legalName'. Legal name must have at least two letters" }
|
||||
}
|
||||
}
|
||||
|
||||
private interface Rule<in T> {
|
||||
fun validate(legalName: T)
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
@file:JvmName("X500NameUtils")
|
||||
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
val X500Name.commonName: String? get() = getRDNValueString(BCStyle.CN)
|
||||
val X500Name.organisationUnit: String? get() = getRDNValueString(BCStyle.OU)
|
||||
val X500Name.state: String? get() = getRDNValueString(BCStyle.ST)
|
||||
val X500Name.organisation: String get() = getRDNValueString(BCStyle.O) ?: throw IllegalArgumentException("Malformed X500 name, organisation attribute (O) cannot be empty.")
|
||||
val X500Name.locality: String get() = getRDNValueString(BCStyle.L) ?: throw IllegalArgumentException("Malformed X500 name, locality attribute (L) cannot be empty.")
|
||||
val X500Name.country: String get() = getRDNValueString(BCStyle.C) ?: throw IllegalArgumentException("Malformed X500 name, country attribute (C) cannot be empty.")
|
||||
|
||||
private fun X500Name.getRDNValueString(identifier: ASN1ObjectIdentifier): String? = getRDNs(identifier).firstOrNull()?.first?.value?.toString()
|
||||
|
||||
val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
|
||||
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
|
||||
|
||||
/**
|
||||
* Generate a distinguished name from the provided X500 .
|
||||
*
|
||||
* @param O organisation name.
|
||||
* @param L locality.
|
||||
* @param C county.
|
||||
* @param CN common name.
|
||||
* @param OU organisation unit.
|
||||
* @param ST state.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun getX500Name(O: String, L: String, C: String, CN: String? = null, OU: String? = null, ST: String? = null): X500Name {
|
||||
return X500NameBuilder(BCStyle.INSTANCE).apply {
|
||||
addRDN(BCStyle.C, C)
|
||||
ST?.let { addRDN(BCStyle.ST, it) }
|
||||
addRDN(BCStyle.L, L)
|
||||
addRDN(BCStyle.O, O)
|
||||
OU?.let { addRDN(BCStyle.OU, it) }
|
||||
CN?.let { addRDN(BCStyle.CN, it) }
|
||||
}.build()
|
||||
}
|
||||
|
||||
fun X500Name.withCommonName(commonName: String?): X500Name {
|
||||
return getX500Name(organisation, locality, country, commonName, organisationUnit, state)
|
||||
}
|
||||
|
||||
fun X500Name.toWellFormattedName(): X500Name {
|
||||
validateX500Name(this)
|
||||
return getX500Name(organisation, locality, country, commonName, organisationUnit, state)
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
@ -1,15 +1,18 @@
|
||||
package net.corda.core.flows;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.testing.node.MockNetwork;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class FlowsInJavaTest {
|
||||
|
||||
@ -18,11 +21,13 @@ public class FlowsInJavaTest {
|
||||
private MockNetwork.MockNode node2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
public void setUp() throws Exception {
|
||||
MockNetwork.BasketOfNodes someNodes = mockNet.createSomeNodes(2);
|
||||
node1 = someNodes.getPartyNodes().get(0);
|
||||
node2 = someNodes.getPartyNodes().get(1);
|
||||
mockNet.runNetwork();
|
||||
// Ensure registration was successful
|
||||
node1.getNodeReadyFuture().get();
|
||||
}
|
||||
|
||||
@After
|
||||
@ -38,6 +43,30 @@ public class FlowsInJavaTest {
|
||||
assertThat(result.get()).isEqualTo("Hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void primitiveClassForReceiveType() throws InterruptedException {
|
||||
// Using the primitive classes causes problems with the checkpointing so we use the wrapper classes and convert
|
||||
// to the primitive class at callsite.
|
||||
for (Class<?> receiveType : Primitives.allWrapperTypes()) {
|
||||
primitiveReceiveTypeTest(receiveType);
|
||||
}
|
||||
}
|
||||
|
||||
private void primitiveReceiveTypeTest(Class<?> receiveType) throws InterruptedException {
|
||||
PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(node2.getInfo().getLegalIdentity(), receiveType);
|
||||
Future<?> result = node1.getServices().startFlow(flow).getResultFuture();
|
||||
mockNet.runNetwork();
|
||||
try {
|
||||
result.get();
|
||||
fail("ExecutionException should have been thrown");
|
||||
} catch (ExecutionException e) {
|
||||
assertThat(e.getCause())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("primitive")
|
||||
.hasMessageContaining(receiveType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
private static class SendInUnwrapFlow extends FlowLogic<String> {
|
||||
private final Party otherParty;
|
||||
@ -71,4 +100,21 @@ public class FlowsInJavaTest {
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
private static class PrimitiveReceiveFlow extends FlowLogic<Void> {
|
||||
private final Party otherParty;
|
||||
private final Class<?> receiveType;
|
||||
|
||||
private PrimitiveReceiveFlow(Party otherParty, Class<?> receiveType) {
|
||||
this.otherParty = otherParty;
|
||||
this.receiveType = receiveType;
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public Void call() throws FlowException {
|
||||
receive(Primitives.unwrap(receiveType), otherParty);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class AttachmentTest {
|
||||
val attachment = object : Attachment {
|
||||
override val id get() = throw UnsupportedOperationException()
|
||||
override fun open() = inputStream
|
||||
override val signers get() = throw UnsupportedOperationException()
|
||||
}
|
||||
try {
|
||||
attachment.openAsJAR()
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -96,7 +96,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
||||
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_NOTARY)
|
||||
val inputs = emptyList<StateAndRef<*>>()
|
||||
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
val commands = emptyList<CommandWithParties<CommandData>>()
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val timeWindow: TimeWindow? = null
|
||||
@ -137,7 +137,7 @@ class TransactionTests : TestDependencyInjectionBase() {
|
||||
val outState = inState.copy(notary = ALICE)
|
||||
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
|
||||
val outputs = listOf(outState)
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
val commands = emptyList<CommandWithParties<CommandData>>()
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val timeWindow: TimeWindow? = null
|
||||
|
@ -1,16 +1,17 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.crypto.composite.CompositeSignature
|
||||
import net.corda.core.crypto.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
|
||||
import net.corda.core.internal.declaredField
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.cert
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import net.corda.testing.kryoSpecific
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -88,7 +89,7 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
||||
val aliceAndBobOrCharlie = CompositeKey.Builder().addKeys(aliceAndBob, charliePublicKey).build(threshold = 1)
|
||||
|
||||
val encoded = aliceAndBobOrCharlie.toBase58String()
|
||||
val decoded = parsePublicKeyBase58(encoded)
|
||||
val decoded = net.corda.core.utilities.parsePublicKeyBase58(encoded)
|
||||
|
||||
assertEquals(decoded, aliceAndBobOrCharlie)
|
||||
}
|
||||
@ -216,7 +217,7 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun `composite key validation with graph cycle detection`() {
|
||||
fun `composite key validation with graph cycle detection`() = kryoSpecific<CompositeKeyTests>("Cycle exists in the object graph which is not currently supported in AMQP mode") {
|
||||
val key1 = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey).build() as CompositeKey
|
||||
val key2 = CompositeKey.Builder().addKeys(alicePublicKey, key1).build() as CompositeKey
|
||||
val key3 = CompositeKey.Builder().addKeys(alicePublicKey, key2).build() as CompositeKey
|
||||
@ -330,10 +331,10 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
|
||||
|
||||
// Create self sign CA.
|
||||
val caKeyPair = Crypto.generateKeyPair()
|
||||
val ca = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test CA"), caKeyPair)
|
||||
val ca = X509Utilities.createSelfSignedCACertificate(getX500Name(CN = "Test CA", O = "R3", L = "London", C = "GB"), caKeyPair)
|
||||
|
||||
// Sign the composite key with the self sign CA.
|
||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca, caKeyPair, X500Name("CN=CompositeKey"), compositeKey)
|
||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca, caKeyPair, getX500Name(CN = "CompositeKey", O = "R3", L = "London", C = "GB"), compositeKey)
|
||||
|
||||
// Store certificate to keystore.
|
||||
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.internal.toTypedArray
|
||||
import net.corda.core.utilities.cert
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import net.corda.node.utilities.*
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
@ -18,13 +20,13 @@ class X509NameConstraintsTest {
|
||||
|
||||
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
|
||||
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(getX509Name("Corda Root CA", "London", "demo@r3.com", null), rootKeys)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(getX500Name(CN = "Corda Root CA", O = "R3CEV", L = "London", C = "GB"), rootKeys)
|
||||
|
||||
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, getX509Name("Corda Intermediate CA", "London", "demo@r3.com", null), intermediateCAKeyPair.public)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, getX500Name(CN = "Corda Intermediate CA", O = "R3CEV", L = "London", C = "GB"), intermediateCAKeyPair.public)
|
||||
|
||||
val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, getX509Name("Corda Client CA", "London", "demo@r3.com", null), clientCAKeyPair.public, nameConstraints = nameConstraints)
|
||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, getX500Name(CN = "Corda Client CA", O = "R3CEV", L = "London", C = "GB"), clientCAKeyPair.public, nameConstraints = nameConstraints)
|
||||
|
||||
val keyPass = "password"
|
||||
val trustStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
|
@ -56,6 +56,11 @@ class AttachmentTests {
|
||||
val nodes = mockNet.createSomeNodes(2)
|
||||
val n0 = nodes.partyNodes[0]
|
||||
val n1 = nodes.partyNodes[1]
|
||||
|
||||
// Ensure that registration was successful before progressing any further
|
||||
mockNet.runNetwork()
|
||||
n0.ensureRegistered()
|
||||
|
||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
|
||||
@ -89,6 +94,11 @@ class AttachmentTests {
|
||||
val nodes = mockNet.createSomeNodes(2)
|
||||
val n0 = nodes.partyNodes[0]
|
||||
val n1 = nodes.partyNodes[1]
|
||||
|
||||
// Ensure that registration was successful before progressing any further
|
||||
mockNet.runNetwork()
|
||||
n0.ensureRegistered()
|
||||
|
||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
|
||||
@ -119,6 +129,10 @@ class AttachmentTests {
|
||||
}, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type)))
|
||||
val n1 = mockNet.createNode(n0.network.myAddress)
|
||||
|
||||
// Ensure that registration was successful before progressing any further
|
||||
mockNet.runNetwork()
|
||||
n0.ensureRegistered()
|
||||
|
||||
n0.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
n1.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||
|
||||
|
@ -36,6 +36,7 @@ class CollectSignaturesFlowTests {
|
||||
c = nodes.partyNodes[2]
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
mockNet.runNetwork()
|
||||
a.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
@ -140,9 +141,13 @@ class CollectSignaturesFlowTests {
|
||||
|
||||
@Test
|
||||
fun `successfully collects two signatures`() {
|
||||
val bConfidentialIdentity = b.services.keyManagementService.freshKeyAndCert(b.info.legalIdentityAndCert, false)
|
||||
// Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity
|
||||
a.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity)
|
||||
val bConfidentialIdentity = b.database.transaction {
|
||||
b.services.keyManagementService.freshKeyAndCert(b.info.legalIdentityAndCert, false)
|
||||
}
|
||||
a.database.transaction {
|
||||
// Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity
|
||||
a.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity)
|
||||
}
|
||||
registerFlowOnAllNodes(TestFlowTwo.Responder::class)
|
||||
val magicNumber = 1337
|
||||
val parties = listOf(a.info.legalIdentity, bConfidentialIdentity.party, c.info.legalIdentity)
|
||||
|
@ -46,11 +46,21 @@ class ContractUpgradeFlowTest {
|
||||
val nodes = mockNet.createSomeNodes(notaryKeyPair = null) // prevent generation of notary override
|
||||
a = nodes.partyNodes[0]
|
||||
b = nodes.partyNodes[1]
|
||||
|
||||
// Process registration
|
||||
mockNet.runNetwork()
|
||||
a.ensureRegistered()
|
||||
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
|
||||
val nodeIdentity = nodes.notaryNode.info.legalIdentitiesAndCerts.single { it.party == nodes.notaryNode.info.notaryIdentity }
|
||||
a.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
a.database.transaction {
|
||||
a.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
}
|
||||
b.database.transaction {
|
||||
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
@ -74,15 +84,24 @@ class ContractUpgradeFlowTest {
|
||||
requireNotNull(btx)
|
||||
|
||||
// The request is expected to be rejected because party B hasn't authorised the upgrade yet.
|
||||
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade.
|
||||
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||
// Party B authorise the contract state upgrade, and immediately deauthorise the same.
|
||||
b.services.startFlow(ContractUpgradeFlow.Authorise(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow()
|
||||
b.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).resultFuture.getOrThrow()
|
||||
|
||||
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
|
||||
val deauthorisedFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade
|
||||
b.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow()
|
||||
|
||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
||||
val resultFuture = a.services.startFlow(ContractUpgradeFlow(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
|
||||
val result = resultFuture.getOrThrow()
|
||||
@ -128,7 +147,10 @@ class ContractUpgradeFlowTest {
|
||||
|
||||
val user = rpcTestUser.copy(permissions = setOf(
|
||||
startFlowPermission<FinalityInvoker>(),
|
||||
startFlowPermission<ContractUpgradeFlow<*, *>>()
|
||||
startFlowPermission<ContractUpgradeFlow.Initiator<*, *>>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Acceptor>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Authorise>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Deauthorise>()
|
||||
))
|
||||
val rpcA = startProxy(a, user)
|
||||
val rpcB = startProxy(b, user)
|
||||
@ -141,18 +163,35 @@ class ContractUpgradeFlowTest {
|
||||
requireNotNull(atx)
|
||||
requireNotNull(btx)
|
||||
|
||||
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
|
||||
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
atx!!.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade, and immediately deauthorise the same.
|
||||
rpcB.startFlow( { stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade ) },
|
||||
btx!!.tx.outRef<ContractState>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
rpcB.startFlow( { stateRef -> ContractUpgradeFlow.Deauthorise(stateRef) },
|
||||
btx.tx.outRef<ContractState>(0).ref).returnValue
|
||||
|
||||
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
|
||||
val deauthorisedFuture = rpcA.startFlow( {stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
atx.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade.
|
||||
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||
rpcB.startFlow( { stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade ) },
|
||||
btx.tx.outRef<ContractState>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
||||
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
|
||||
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
atx.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
@ -184,7 +223,7 @@ class ContractUpgradeFlowTest {
|
||||
val baseState = a.database.transaction { a.services.vaultQueryService.queryBy<ContractState>().states.single() }
|
||||
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
||||
// Starts contract upgrade flow.
|
||||
val upgradeResult = a.services.startFlow(ContractUpgradeFlow(stateAndRef, CashV2::class.java)).resultFuture
|
||||
val upgradeResult = a.services.startFlow(ContractUpgradeFlow.Initiator(stateAndRef, CashV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
upgradeResult.getOrThrow()
|
||||
// Get contract state from the vault.
|
||||
|
@ -29,6 +29,7 @@ class FinalityFlowTests {
|
||||
nodeB = nodes.partyNodes[1]
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
mockNet.runNetwork()
|
||||
nodeA.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -41,10 +41,14 @@ class IdentitySyncFlowTests {
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
||||
val bob: Party = bobNode.services.myInfo.legalIdentity
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
aliceNode.database.transaction {
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
}
|
||||
bobNode.database.transaction {
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
}
|
||||
bobNode.registerInitiatedFlow(Receive::class.java)
|
||||
|
||||
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
|
||||
@ -53,12 +57,16 @@ class IdentitySyncFlowTests {
|
||||
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notaryNode.services.myInfo.notaryIdentity))
|
||||
val issueTx = issueFlow.resultFuture.getOrThrow().stx
|
||||
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
|
||||
assertNull(bobNode.services.identityService.partyFromAnonymous(confidentialIdentity))
|
||||
assertNull(bobNode.database.transaction { bobNode.services.identityService.partyFromAnonymous(confidentialIdentity) })
|
||||
|
||||
// Run the flow to sync up the identities
|
||||
aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).resultFuture.getOrThrow()
|
||||
val expected = aliceNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
val actual = bobNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
val expected = aliceNode.database.transaction {
|
||||
aliceNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
}
|
||||
val actual = bobNode.database.transaction {
|
||||
bobNode.services.identityService.partyFromAnonymous(confidentialIdentity)
|
||||
}
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ class ManualFinalityFlowTests {
|
||||
nodeC = nodes.partyNodes[2]
|
||||
notary = nodes.notaryNode.info.notaryIdentity
|
||||
mockNet.runNetwork()
|
||||
nodeA.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -26,10 +26,14 @@ class TransactionKeyFlowTests {
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
||||
val bob: Party = bobNode.services.myInfo.legalIdentity
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
aliceNode.database.transaction {
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
}
|
||||
bobNode.database.transaction {
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
}
|
||||
|
||||
// Run the flows
|
||||
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
|
||||
@ -44,8 +48,8 @@ class TransactionKeyFlowTests {
|
||||
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity)
|
||||
|
||||
// Verify that the anonymous identities look sane
|
||||
assertEquals(alice.name, aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name)
|
||||
assertEquals(bob.name, bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name)
|
||||
assertEquals(alice.name, aliceNode.database.transaction { aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name })
|
||||
assertEquals(bob.name, bobNode.database.transaction { bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name })
|
||||
|
||||
// Verify that the nodes have the right anonymous identities
|
||||
assertTrue { aliceAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
|
||||
|
@ -3,10 +3,10 @@ package net.corda.core.identity
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import net.corda.testing.getTestPartyAndCertificate
|
||||
import net.corda.testing.withTestSerialization
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
|
||||
@ -15,7 +15,7 @@ class PartyAndCertificateTest {
|
||||
fun `kryo serialisation`() {
|
||||
withTestSerialization {
|
||||
val original = getTestPartyAndCertificate(Party(
|
||||
X500Name("CN=Test Corp,O=Test Corp,L=Madrid,C=ES"),
|
||||
getX500Name(O = "Test Corp", L = "Madrid", C = "ES"),
|
||||
entropyToKeyPair(BigInteger.valueOf(83)).public))
|
||||
val copy = original.serialize().deserialize()
|
||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||
|
@ -0,0 +1,118 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.AfterClass
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AbstractAttachmentTest {
|
||||
companion object {
|
||||
private val dir = Files.createTempDirectory(AbstractAttachmentTest::class.simpleName)
|
||||
private val bin = Paths.get(System.getProperty("java.home")).let { if (it.endsWith("jre")) it.parent else it } / "bin"
|
||||
private val shredder = (dir / "_shredder").toFile() // No need to delete after each test.
|
||||
fun execute(vararg command: String) {
|
||||
assertEquals(0, ProcessBuilder()
|
||||
.inheritIO()
|
||||
.redirectOutput(shredder)
|
||||
.directory(dir.toFile())
|
||||
.command((bin / command[0]).toString(), *command.sliceArray(1 until command.size))
|
||||
.start()
|
||||
.waitFor())
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun beforeClass() {
|
||||
execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", "alice", "-keypass", "alicepass", "-dname", ALICE.toString())
|
||||
execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", "bob", "-keypass", "bobpass", "-dname", BOB.toString())
|
||||
(dir / "_signable1").writeLines(listOf("signable1"))
|
||||
(dir / "_signable2").writeLines(listOf("signable2"))
|
||||
(dir / "_signable3").writeLines(listOf("signable3"))
|
||||
}
|
||||
|
||||
private fun load(name: String) = object : AbstractAttachment({ (dir / name).readAll() }) {
|
||||
override val id get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic
|
||||
fun afterClass() {
|
||||
dir.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
dir.toFile().listFiles().forEach {
|
||||
if (!it.name.startsWith("_")) it.deleteRecursively()
|
||||
}
|
||||
assertEquals(5, dir.toFile().listFiles().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty jar has no signers`() {
|
||||
(dir / "META-INF").createDirectory() // At least one arg is required, and jar cvf conveniently ignores this.
|
||||
execute("jar", "cvf", "attachment.jar", "META-INF")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers) // There needs to have been a file for ALICE to sign.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unsigned jar has no signers`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||
execute("jar", "uvf", "attachment.jar", "_signable2")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `one signer`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1", "_signable2")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name }) // We only reused ALICE's distinguished name, so the keys will be different.
|
||||
(dir / "my-dir").createDirectory()
|
||||
execute("jar", "uvf", "attachment.jar", "my-dir")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name }) // Unsigned directory is irrelevant.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two signers`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1", "_signable2")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||
assertEquals(listOf(ALICE.name, BOB.name), load("attachment.jar").signers.map { it.name })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a party must sign all the files in the attachment to be a signer`() {
|
||||
execute("jar", "cvf", "attachment.jar", "_signable1")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name })
|
||||
execute("jar", "uvf", "attachment.jar", "_signable2")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||
assertEquals(listOf(BOB.name), load("attachment.jar").signers.map { it.name }) // ALICE hasn't signed the new file.
|
||||
execute("jar", "uvf", "attachment.jar", "_signable3")
|
||||
assertEquals(emptyList(), load("attachment.jar").signers) // Neither party has signed the new file.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bad signature is caught even if the party would not qualify as a signer`() {
|
||||
(dir / "volatile").writeLines(listOf("volatile"))
|
||||
execute("jar", "cvf", "attachment.jar", "volatile")
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "alicepass", "attachment.jar", "alice")
|
||||
assertEquals(listOf(ALICE.name), load("attachment.jar").signers.map { it.name })
|
||||
(dir / "volatile").writeLines(listOf("garbage"))
|
||||
execute("jar", "uvf", "attachment.jar", "volatile", "_signable1") // ALICE's signature on volatile is now bad.
|
||||
execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", "bobpass", "attachment.jar", "bob")
|
||||
val a = load("attachment.jar")
|
||||
// The JDK doesn't care that BOB has correctly signed the whole thing, it won't let us process the entry with ALICE's bad signature:
|
||||
assertThatThrownBy { a.signers }.isInstanceOf(SecurityException::class.java)
|
||||
}
|
||||
}
|
@ -112,6 +112,18 @@ class CordaFutureTest {
|
||||
}
|
||||
verify(log).error(any(), same(throwable))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `captureLater works`() {
|
||||
val failingFuture = CordaFutureImpl<Int>()
|
||||
val anotherFailingFuture = CordaFutureImpl<Int>()
|
||||
anotherFailingFuture.captureLater(failingFuture)
|
||||
|
||||
val exception = Exception()
|
||||
failingFuture.setException(exception)
|
||||
|
||||
Assertions.assertThatThrownBy { anotherFailingFuture.getOrThrow() }.isSameAs(exception)
|
||||
}
|
||||
}
|
||||
|
||||
class TransposeTest {
|
||||
|
@ -2,14 +2,14 @@ package net.corda.core.node
|
||||
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.testing.getTestX509Name
|
||||
import net.corda.core.utilities.getX500Name
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class ServiceInfoTests {
|
||||
val serviceType = ServiceType.getServiceType("test", "service").getSubType("subservice")
|
||||
val name = getTestX509Name("service.name")
|
||||
val name = getX500Name(O = "service.name", L = "London", C = "GB")
|
||||
|
||||
@Test
|
||||
fun `type and name encodes correctly`() {
|
||||
|
@ -46,13 +46,13 @@ private fun MockNetwork.MockNode.saveAttachment(content: String) = database.tran
|
||||
attachments.importAttachment(createAttachmentData(content).inputStream())
|
||||
}
|
||||
private fun MockNetwork.MockNode.hackAttachment(attachmentId: SecureHash, content: String) = database.transaction {
|
||||
attachments.updateAttachment(attachmentId, createAttachmentData(content))
|
||||
updateAttachment(attachmentId, createAttachmentData(content))
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeAttachmentService.importAttachment
|
||||
*/
|
||||
private fun NodeAttachmentService.updateAttachment(attachmentId: SecureHash, data: ByteArray) {
|
||||
private fun updateAttachment(attachmentId: SecureHash, data: ByteArray) {
|
||||
val session = DatabaseTransactionManager.current().session
|
||||
val attachment = session.get<NodeAttachmentService.DBAttachment>(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString())
|
||||
attachment?.let {
|
||||
@ -73,6 +73,7 @@ class AttachmentSerializationTest {
|
||||
client = mockNet.createNode(server.network.myAddress)
|
||||
client.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
|
||||
mockNet.runNetwork()
|
||||
server.ensureRegistered()
|
||||
}
|
||||
|
||||
@After
|
||||
@ -112,6 +113,7 @@ class AttachmentSerializationTest {
|
||||
|
||||
private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment {
|
||||
override fun open() = throw UnsupportedOperationException("Not implemented.")
|
||||
override val signers get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private class CustomAttachmentLogic(server: MockNetwork.MockNode, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(server) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.crypto
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.crypto.AddressFormatException
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
@ -52,8 +52,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
private class CapturingTransientProperty(prefix: String) {
|
||||
private val seed = random63BitValue()
|
||||
private class CapturingTransientProperty(val prefix: String, val seed: Long = random63BitValue()) {
|
||||
val transientVal by transient { prefix + seed + random63BitValue() }
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.utilities
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
@ -99,4 +100,33 @@ class LegalNameValidatorTest {
|
||||
validateLegalName("Legal Name With\n\rLine\nBreaks")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validate x500Name`() {
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US, CN=Service Name"))
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US"))
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, C=US"))
|
||||
|
||||
// Missing Organisation
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Missing Locality
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=Bank A, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Missing Country
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=Bank A, L=New York, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Wrong organisation name format
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
// Wrong organisation name format
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateX500Name(X500Name("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name"))
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,18 @@ dokka {
|
||||
moduleName = 'corda'
|
||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin")
|
||||
processConfigurations = ['compile']
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node-api/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
jdkVersion = 8
|
||||
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://fasterxml.github.io/jackson-core/javadoc/2.8/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://docs.oracle.com/javafx/2/api/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://www.bouncycastle.org/docs/docs1.5on/")
|
||||
}
|
||||
}
|
||||
|
||||
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
@ -17,8 +28,19 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
outputFormat = "javadoc"
|
||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
|
||||
processConfigurations = ['compile']
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node-api/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
|
||||
includes = ['packages.md']
|
||||
jdkVersion = 8
|
||||
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://fasterxml.github.io/jackson-core/javadoc/2.8/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://docs.oracle.com/javafx/2/api/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://www.bouncycastle.org/docs/docs1.5on/")
|
||||
}
|
||||
}
|
||||
|
||||
task buildDocs(dependsOn: ['apidocs', 'makeDocs'])
|
||||
|
103
docs/packages.md
103
docs/packages.md
@ -1,3 +1,104 @@
|
||||
# Package net.corda.client.jackson
|
||||
|
||||
Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
|
||||
the java.time API, some core types, and Kotlin data classes.
|
||||
|
||||
# Package net.corda.client.jfx.model
|
||||
|
||||
Data models for binding data feeds from Corda nodes into a JavaFX user interface, by presenting the data as [javafx.beans.Observable]
|
||||
types.
|
||||
|
||||
# Package net.corda.client.jfx.utils
|
||||
|
||||
Utility classes (i.e. data classes) used by the Corda JavaFX client.
|
||||
|
||||
# Package net.corda.client.mock
|
||||
|
||||
Tools used by the client to produce mock data for testing purposes.
|
||||
|
||||
# Package net.corda.client.rpc
|
||||
|
||||
RPC client interface to Corda, for use both by user-facing client and integration with external systems.
|
||||
|
||||
# Package net.corda.client.rpc.internal
|
||||
|
||||
Internal, do not use. These APIs and implementations which are currently being revised and are subject to future change.
|
||||
|
||||
# Package net.corda.core.concurrent
|
||||
|
||||
Provides a simplified [java.util.concurrent.Future] class that allows registration of a callback to execute when the future
|
||||
is complete.
|
||||
|
||||
# Package net.corda.core.contracts
|
||||
|
||||
This package contains the base data types for smarts contracts implemented in Corda. To implement a new contract start
|
||||
with [Contract], or see the examples in [net.corda.finance.contracts].
|
||||
|
||||
Corda smart contracts are a combination of state held on the distributed ledger, and verification logic which defines
|
||||
which transformations of state are valid.
|
||||
|
||||
# Package net.corda.core.crypto
|
||||
|
||||
Cryptography data and utility classes used for signing, verifying, key management and data integrity checks.
|
||||
|
||||
# Package net.corda.core.flows
|
||||
|
||||
Base data types and abstract classes for implementing Corda flows. To implement a new flow start with [FlowLogic], or
|
||||
see [CollectSignaturesFlow] for a simple example flow. Flows are started via a node's [ServiceHub].
|
||||
|
||||
Corda flows are a tool for modelling the interactions between two or more nodes as they negotiate a workflow.
|
||||
This can range from a simple case of completing a trade which has been agreed upon externally, to more complex
|
||||
processes such as handling fixing of interest rate swaps.
|
||||
|
||||
# Package net.corda.core.identity
|
||||
|
||||
Data classes which model different forms of identity (potentially with supporting evidence) for legal entities and services.
|
||||
|
||||
# Package net.corda.core.internal
|
||||
|
||||
Internal, do not use. These APIs and implementations which are currently being revised and are subject to future change.
|
||||
|
||||
# Package net.corda.core.messaging
|
||||
|
||||
Data types used by the Corda messaging layer to manage state of messaging and sessions between nodes.
|
||||
|
||||
# Package net.corda.core.node.services
|
||||
|
||||
Services which run within a Corda node and provide various pieces of functionality such as identity management, transaction storage, etc.
|
||||
|
||||
# Package net.corda.core.node.services.vault
|
||||
|
||||
Supporting data types for the vault services.
|
||||
|
||||
# Package net.corda.core.schemas
|
||||
|
||||
Data types representing database schemas for storing Corda data via an object mapper such as Hibernate. Modifying Corda
|
||||
state in the database directly is not a supported approach, however these can be used to read state for integrations with
|
||||
external systems.
|
||||
|
||||
# Package net.corda.core.serialization
|
||||
|
||||
Supporting data types and classes for serialization of Corda data types.
|
||||
|
||||
# Package net.corda.core.transactions
|
||||
|
||||
Base data types for transactions which modify contract state on the distributed ledger.
|
||||
|
||||
The core transaction on the ledger is [WireTransaction], which is constructed by [TransactionBuilder]. Once signed a transaction is stored
|
||||
in [SignedTransaction] which encapsulates [WireTransaction]. Finally there is a special-case [LedgerTransaction] which is used by contracts
|
||||
validating transactions, and is built from the wire transaction by resolving all references into their underlying data (i.e. inputs are
|
||||
actual states rather than state references).
|
||||
|
||||
# Package net.corda.core.utilities
|
||||
|
||||
Corda utility classes, providing a broad range of functionality to help implement both Corda nodes and CorDapps.
|
||||
|
||||
# Package net.corda.finance
|
||||
|
||||
The finance module is a CorDapp containing sample cash and obligation contracts, as well as providing several
|
||||
useful data types such as [Amount].
|
||||
|
||||
# Package net.corda.finance.utils
|
||||
|
||||
A collection of utilities for summing financial states, for example, summing obligations to get total debts.
|
||||
A collection of utilities for summing financial states, for example, summing obligations to get total debts.
|
||||
|
||||
|
@ -132,9 +132,9 @@ exception will cause the transaction to be rejected.
|
||||
Commands
|
||||
^^^^^^^^
|
||||
|
||||
``LedgerTransaction`` contains the commands as a list of ``AuthenticatedObject`` instances.
|
||||
``AuthenticatedObject`` pairs an object with a list of signers. In this case, ``AuthenticatedObject`` pairs a command
|
||||
with a list of the entities that are required to sign a transaction where this command is present:
|
||||
``LedgerTransaction`` contains the commands as a list of ``CommandWithParties`` instances.
|
||||
``CommandWithParties`` pairs a command with a list of the entities that are required to sign a transaction
|
||||
where this command is present:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -153,7 +153,7 @@ Extracting commands
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
You can use the ``requireSingleCommand()`` helper method to extract commands.
|
||||
|
||||
``<C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand(klass: Class<C>)`` asserts that
|
||||
``<C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>)`` asserts that
|
||||
the transaction contains exactly one command of type ``T``, and returns it. If there is not exactly one command of this
|
||||
type in the transaction, an exception is thrown, rejecting the transaction.
|
||||
|
||||
@ -197,7 +197,7 @@ execution of ``verify()``:
|
||||
|
||||
@Override
|
||||
public void verify(LedgerTransaction tx) {
|
||||
final AuthenticatedObject<Commands> command = requireSingleCommand(tx.getCommands(), Commands.class);
|
||||
final CommandWithParties<Commands> command = requireSingleCommand(tx.getCommands(), Commands.class);
|
||||
|
||||
if (command.getValue() instanceof Commands.Issue) {
|
||||
// Issuance verification logic.
|
||||
|
@ -40,9 +40,9 @@ anonymous parties to full parties.
|
||||
.. note:: These types are provisional and will change significantly in future as the identity framework becomes more
|
||||
fleshed out.
|
||||
|
||||
AuthenticatedObject
|
||||
-------------------
|
||||
An ``AuthenticatedObject`` represents an object (like a command) and the list of associated signers.
|
||||
CommandWithParties
|
||||
------------------
|
||||
A ``CommandWithParties`` represents a command and the list of associated signers' identities.
|
||||
|
||||
Multi-signature support
|
||||
-----------------------
|
||||
|
@ -196,8 +196,13 @@ updates. This section details the API for common tasks.
|
||||
|
||||
Transaction building
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
The majority of the work performed during a flow will be to build, verify and sign a transaction. We cover this in
|
||||
:doc:`api-transactions`.
|
||||
The majority of the work performed during a flow will be to build, verify and sign a transaction. This is covered
|
||||
in :doc:`api-transactions`.
|
||||
|
||||
Extracting states from the vault
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
When building a transaction, you'll often need to extract the states you wish to consume from the vault. This is
|
||||
covered in :doc:`api-vault-query`.
|
||||
|
||||
Retrieving information about other nodes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -592,4 +597,4 @@ We then update the progress tracker's current step as we progress through the fl
|
||||
:language: java
|
||||
:start-after: DOCSTART 18
|
||||
:end-before: DOCEND 18
|
||||
:dedent: 12
|
||||
:dedent: 12
|
||||
|
@ -17,9 +17,9 @@ The ORM mapping is specified using the `Java Persistence API <https://en.wikiped
|
||||
in the node's local vault as part of a transaction.
|
||||
|
||||
.. note:: Presently the node includes an instance of the H2 database but any database that supports JDBC is a
|
||||
candidate and the node will in the future support a range of database implementations via their JDBC drivers. Much
|
||||
of the node internal state is also persisted there. You can access the internal H2 database via JDBC, please see the
|
||||
info in ":doc:`node-administration`" for details.
|
||||
candidate and the node will in the future support a range of database implementations via their JDBC drivers. Much
|
||||
of the node internal state is also persisted there. You can access the internal H2 database via JDBC, please see the
|
||||
info in ":doc:`node-administration`" for details.
|
||||
|
||||
Schemas
|
||||
-------
|
||||
@ -100,6 +100,8 @@ processed to ensure only the ``X500Name`` of the identity is persisted where an
|
||||
value is stored in the associated column. To preserve privacy, identity keys are never persisted. Developers should use
|
||||
the ``IdentityService`` to resolve keys from well know X500 identity names.
|
||||
|
||||
.. _jdbc_session_ref:
|
||||
|
||||
JDBC session
|
||||
------------
|
||||
Apps may also interact directly with the underlying Node's database by using a standard
|
||||
|
@ -6,6 +6,7 @@ Corda has been architected from the ground up to encourage usage of industry sta
|
||||
Corda provides a number of flexible query mechanisms for accessing the Vault:
|
||||
|
||||
- Vault Query API
|
||||
- using a JDBC session (as described in :ref:`Persistence <jdbc_session_ref>`)
|
||||
- custom JPA_/JPQL_ queries
|
||||
- custom 3rd party Data Access frameworks such as `Spring Data <http://projects.spring.io/spring-data>`_
|
||||
|
||||
@ -62,7 +63,7 @@ There are four implementations of this interface which can be chained together t
|
||||
|
||||
.. note:: All contract states that extend the ``FungibleAsset`` now automatically persist that interfaces common state attributes to the **vault_fungible_states** table.
|
||||
|
||||
3. ``LinearStateQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``LinearState`` and ``DealState`` contract state interfaces, used to represent entities that continuously supercede themselves, all of which share the same *linearId* (eg. trade entity states such as the ``IRSState`` defined in the SIMM valuation demo). Filterable attributes include: participant(s), linearId(s), dealRef(s).
|
||||
3. ``LinearStateQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``LinearState`` and ``DealState`` contract state interfaces, used to represent entities that continuously supercede themselves, all of which share the same *linearId* (eg. trade entity states such as the ``IRSState`` defined in the SIMM valuation demo). Filterable attributes include: participant(s), linearId(s), uuid(s), and externalId(s).
|
||||
|
||||
.. note:: All contract states that extend ``LinearState`` or ``DealState`` now automatically persist those interfaces common state attributes to the **vault_linear_states** table.
|
||||
|
||||
@ -70,14 +71,14 @@ There are four implementations of this interface which can be chained together t
|
||||
|
||||
.. note:: It is a requirement to register any custom contract schemas to be used in Vault Custom queries in the associated `CordaPluginRegistry` configuration for the respective CorDapp using the ``requiredSchemas`` configuration field (which specifies a set of `MappedSchema`)
|
||||
|
||||
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators, as also illustrated above.
|
||||
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators.
|
||||
|
||||
All ``QueryCriteria`` implementations provide an explicitly specifiable set of common attributes:
|
||||
|
||||
1. State status attribute (``Vault.StateStatus``), which defaults to filtering on UNCONSUMED states.
|
||||
When chaining several criterias using AND / OR, the last value of this attribute will override any previous.
|
||||
2. Contract state types (``<Set<Class<out ContractState>>``), which will contain at minimum one type (by default this will be ``ContractState`` which resolves to all types).
|
||||
When chaining several criteria using AND / OR, all specified contract state types are combined into a single set.
|
||||
2. Contract state types (``<Set<Class<out ContractState>>``), which will contain at minimum one type (by default this will be ``ContractState`` which resolves to all state types).
|
||||
When chaining several criteria using ``and`` and ``or`` operators, all specified contract state types are combined into a single set.
|
||||
|
||||
An example of a custom query is illustrated here:
|
||||
|
||||
@ -107,7 +108,7 @@ An example of a custom query in Java is illustrated here:
|
||||
:start-after: DOCSTART VaultJavaQueryExample3
|
||||
:end-before: DOCEND VaultJavaQueryExample3
|
||||
|
||||
.. note:: Current queries by ``Party`` specify the ``AbstractParty`` which may be concrete or anonymous. In the later case, where an anonymous party does not have an associated X500Name, then no query results will ever be produced. For performance reasons, queries do not use PublicKey as search criteria. Ongoing design work on identity manangement is likely to enhance identity based queries (including composite key criteria selection).
|
||||
.. note:: Queries by ``Party`` specify the ``AbstractParty`` which may be concrete or anonymous. In the later case, where an anonymous party does not resolve to an X500Name via the IdentityService, no query results will ever be produced. For performance reasons, queries do not use PublicKey as search criteria.
|
||||
|
||||
Pagination
|
||||
----------
|
||||
@ -154,8 +155,6 @@ Query for unconsumed states for a given notary:
|
||||
:start-after: DOCSTART VaultQueryExample4
|
||||
:end-before: DOCEND VaultQueryExample4
|
||||
|
||||
.. note:: We are using the notaries X500Name as our search identifier.
|
||||
|
||||
Query for unconsumed states for a given set of participants:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
||||
@ -170,7 +169,7 @@ Query for unconsumed states recorded between two time intervals:
|
||||
:start-after: DOCSTART VaultQueryExample6
|
||||
:end-before: DOCEND VaultQueryExample6
|
||||
|
||||
.. note:: This example illustrates usage of a Between ColumnPredicate.
|
||||
.. note:: This example illustrates usage of a ``Between`` ``ColumnPredicate``.
|
||||
|
||||
Query for all states with pagination specification (10 results per page):
|
||||
|
||||
@ -179,7 +178,14 @@ Query for all states with pagination specification (10 results per page):
|
||||
:start-after: DOCSTART VaultQueryExample7
|
||||
:end-before: DOCEND VaultQueryExample7
|
||||
|
||||
.. note:: The result set metadata field `totalStatesAvailable` allows you to further paginate accordingly.
|
||||
.. note:: The result set metadata field `totalStatesAvailable` allows you to further paginate accordingly as demonstrated in the following example.
|
||||
|
||||
Query for all states using pagination specification and iterate using `totalStatesAvailable` field until no further pages available:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART VaultQueryExamplePaging
|
||||
:end-before: DOCEND VaultQueryExamplePaging
|
||||
|
||||
**LinearState and DealState queries using** ``LinearStateQueryCriteria``
|
||||
|
||||
@ -389,77 +395,4 @@ The Corda Tutorials provide examples satisfying these additional Use Cases:
|
||||
.. _JPQL: http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql
|
||||
.. _JPA: https://docs.spring.io/spring-data/jpa/docs/current/reference/html
|
||||
|
||||
Upgrading from previous releases
|
||||
---------------------------------
|
||||
|
||||
Here follows a selection of the most common upgrade scenarios:
|
||||
|
||||
1. ServiceHub usage to obtain Unconsumed states for a given contract state type
|
||||
|
||||
Previously:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val yoStates = b.vault.unconsumedStates<Yo.State>()
|
||||
|
||||
This query returned an ``Iterable<StateAndRef<T>>``
|
||||
|
||||
Now:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val yoStates = b.vault.queryBy<Yo.State>().states
|
||||
|
||||
The query returns a ``Vault.Page`` result containing:
|
||||
|
||||
- states as a ``List<StateAndRef<T : ContractState>>`` up to a maximum of ``DEFAULT_PAGE_SIZE`` (200) where no ``PageSpecification`` provided, otherwise returns results according to the parameters ``pageNumber`` and ``pageSize`` specified in the supplied ``PageSpecification``.
|
||||
- states metadata as a ``List<Vault.StateMetadata>`` containing Vault State metadata held in the Vault states table.
|
||||
- a ``total`` number of results available if ``PageSpecification`` provided (otherwise returns -1). For pagination, this value can be used to issue subsequent queries with appropriately specified ``PageSpecification`` parameters (according to your paging needs and/or maximum memory capacity for holding large data sets). Note it is your responsibility to manage page numbers and sizes.
|
||||
- status types used in this query: UNCONSUMED, CONSUMED, ALL
|
||||
- other results as a [List] of any type (eg. aggregate function results with/without group by)
|
||||
|
||||
2. ServiceHub usage obtaining linear heads for a given contract state type
|
||||
|
||||
Previously:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val iouStates = serviceHub.vaultService.linearHeadsOfType<IOUState>()
|
||||
val iouToSettle = iouStates[linearId] ?: throw Exception("IOUState with linearId $linearId not found.")
|
||||
|
||||
Now:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val criteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(linearId))
|
||||
val iouStates = serviceHub.vaultService.queryBy<IOUState>(criteria).states
|
||||
|
||||
val iouToSettle = iouStates.singleOrNull() ?: throw Exception("IOUState with linearId $linearId not found.")
|
||||
|
||||
3. RPC usage was limited to using the ``vaultAndUpdates`` RPC method, which returned a snapshot and streaming updates as an Observable.
|
||||
In many cases, queries were not interested in the streaming updates.
|
||||
|
||||
Previously:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val iouStates = services.vaultAndUpdates().first.filter { it.state.data is IOUState }
|
||||
|
||||
Now:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val iouStates = services.vaultQueryBy<IOUState>()
|
||||
|
||||
|
@ -6,6 +6,12 @@ from the previous milestone release.
|
||||
|
||||
UNRELEASED
|
||||
----------
|
||||
* About half of the code in test-utils has been moved to a new module ``node-driver``,
|
||||
and the test scope modules are now located in a ``testing`` directory.
|
||||
|
||||
* Contract Upgrades: deprecated RPC authorisation / deauthorisation API calls in favour of equivalent flows in ContractUpgradeFlow.
|
||||
Implemented contract upgrade persistence using JDBC backed persistent map.
|
||||
|
||||
* Vault query common attributes (state status and contract state types) are now handled correctly when using composite
|
||||
criteria specifications. State status is overridable. Contract states types are aggregatable.
|
||||
|
||||
@ -34,7 +40,6 @@ UNRELEASED
|
||||
must use ``SendTransactionFlow`` at the correct place. There is also ``ReceiveStateAndRefFlow`` and ``SendStateAndRefFlow`` for
|
||||
dealing with ``StateAndRef``s.
|
||||
|
||||
|
||||
* Vault query soft locking enhancements and deprecations
|
||||
* removed original ``VaultService`` ``softLockedStates` query mechanism.
|
||||
* introduced improved ``SoftLockingCondition`` filterable attribute in ``VaultQueryCriteria`` to enable specification
|
||||
@ -68,6 +73,59 @@ UNRELEASED
|
||||
* Moved ``:finance`` gradle project files into a ``net.corda.finance`` package namespace.
|
||||
This may require adjusting imports of Cash flow references and also of ``StartFlow`` permission in ``gradle.build`` files.
|
||||
|
||||
* Removed the concept of relevancy from ``LinearState``. The ``ContractState``'s relevancy to the vault can be determined
|
||||
by the flow context, the vault will process any transaction from a flow which is not derived from transaction resolution verification.
|
||||
|
||||
* Removed the tolerance attribute from ``TimeWindowChecker`` and thus, there is no extra tolerance on the notary side anymore.
|
||||
|
||||
* The ``FungibleAsset`` interface has been made simpler. The ``Commands`` grouping interface
|
||||
that included the ``Move``, ``Issue`` and ``Exit`` interfaces have all been removed, while the ``move`` function has
|
||||
been renamed to ``withNewOwnerAndAmount`` to be consistent with the ``withNewOwner`` function of the ``OwnableState``.
|
||||
|
||||
* The ``IssueCommand`` interface has been removed from ``Structures``, because, due to the introduction of nonces per
|
||||
transaction component, the issue command does not need a nonce anymore and it does not require any other attributes.
|
||||
|
||||
* As a consequence of the above and the simpler ``FungibleAsset`` format, fungible assets like ``Cash`` now use
|
||||
``class Issue : TypeOnlyCommandData()``, because it's only its presence (``Issue``) that matters.
|
||||
|
||||
* A new `PrivacySalt` transaction component is introduced, which is now an attribute in ``TraversableTransaction`` and
|
||||
inherently in ``WireTransaction``.
|
||||
|
||||
* A new ``nonces: List<SecureHash>`` feature has been added to ``FilteredLeaves``.
|
||||
|
||||
* Due to the ``nonces`` and ``PrivacySalt`` introduction, new functions have been added to ``MerkleTransaction``:
|
||||
``fun <T : Any> serializedHash(x: T, privacySalt: PrivacySalt?, index: Int): SecureHash``
|
||||
``fun <T : Any> serializedHash(x: T, nonce: SecureHash): SecureHash``
|
||||
``fun computeNonce(privacySalt: PrivacySalt, index: Int)``.
|
||||
|
||||
* A new ``SignatureMetadata`` data class is introduced with two attributes, ``platformVersion: Int`` and
|
||||
``schemeNumberID: Int`` (the signature scheme used).
|
||||
|
||||
* As part of the metadata support in signatures, a new ``data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)``
|
||||
is introduced, which represents the object actually signed.
|
||||
|
||||
* The unused ``MetaData`` and ``SignatureType`` in ``crypto`` package have been removed.
|
||||
|
||||
* The ``class TransactionSignature(bytes: ByteArray, val by: PublicKey, val signatureMetadata: SignatureMetadata): DigitalSignature(bytes)``
|
||||
class is now utilised Vs the old ``DigitalSignature.WithKey`` for Corda transaction signatures. Practically, it takes
|
||||
the ``signatureMetadata`` as an extra input, in order to support signing both the transaction and the extra metadata.
|
||||
|
||||
* To reflect changes in the signing process, the ``Crypto`` object is now equipped with the:
|
||||
``fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature`` and
|
||||
``fun doVerify(txId: SecureHash, transactionSignature: TransactionSignature): Boolean`` functions.
|
||||
|
||||
* ``SerializationCustomization.addToWhitelist()` now accepts multiple classes via varargs.
|
||||
|
||||
* Two functions to easily sign a ``FilteredTransaction`` have been added to ``ServiceHub``:
|
||||
``createSignature(filteredTransaction: FilteredTransaction, publicKey: PublicKey)`` and
|
||||
``createSignature(filteredTransaction: FilteredTransaction)`` to sign with the legal identity key.
|
||||
|
||||
* A new helper method ``buildFilteredTransaction(filtering: Predicate<Any>)`` is added to ``SignedTransaction`` to
|
||||
directly build a ``FilteredTransaction`` using provided filtering functions, without first accessing the
|
||||
``tx: WireTransaction``.
|
||||
|
||||
* Test type ``NodeHandle`` now has method ``stop(): CordaFuture<Unit>`` that terminates the referenced node.
|
||||
|
||||
Milestone 14
|
||||
------------
|
||||
|
||||
|
@ -21,7 +21,7 @@ Let's take a look at a simplified structure of the ``Clause`` class:
|
||||
abstract fun verify(tx: LedgerTransaction,
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
commands: List<AuthenticatedObject<C>>,
|
||||
commands: List<CommandWithParties<C>>,
|
||||
groupingKey: K?): Set<C>
|
||||
...
|
||||
}
|
||||
@ -235,7 +235,7 @@ Example from ``CommercialPaper.kt``:
|
||||
override fun verify(tx: LedgerTransaction,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
commands: List<CommandWithParties<Commands>>,
|
||||
groupingKey: Issued<Terms>?): Set<Commands> {
|
||||
val consumedCommands = super.verify(tx, inputs, outputs, commands, groupingKey)
|
||||
...
|
||||
|
@ -21,23 +21,21 @@ Here's the workflow for contract upgrades:
|
||||
|
||||
1. Two banks, A and B negotiate a trade, off-platform
|
||||
|
||||
2. Banks A and B execute a protocol to construct a state object representing the trade, using contract X, and include it in a transaction (which is then signed and sent to the Uniqueness Service).
|
||||
2. Banks A and B execute a protocol to construct a state object representing the trade, using contract X, and include it in a transaction (which is then signed and sent to the consensus service).
|
||||
|
||||
3. Time passes.
|
||||
|
||||
4. The developer of contract X discovers a bug in the contract code, and releases a new version, contract Y.
|
||||
And notify the users (e.g. via a mailing list or CorDapp store).
|
||||
At this point of time all nodes should stop issuing states of contract X.
|
||||
4. The developer of contract X discovers a bug in the contract code, and releases a new version, contract Y. The developer will then notify all existing users (e.g. via a mailing list or CorDapp store) to stop their nodes from issuing further states with contract X.
|
||||
|
||||
5. Banks A and B review the new contract via standard change control processes and identify the contract states they agreed to upgrade, they can decide not to upgrade some contract states as they might be needed for other obligation contract.
|
||||
5. Banks A and B review the new contract via standard change control processes and identify the contract states they agree to upgrade (they may decide not to upgrade some contract states as these might be needed for some other obligation contract).
|
||||
|
||||
6. Banks A and B instruct their Corda nodes (via RPC) to be willing to upgrade state objects of contract X, to state objects for contract Y using agreed upgrade path.
|
||||
|
||||
7. One of the parties ``Instigator`` initiates an upgrade of state objects referring to contract X, to a new state object referring to contract Y.
|
||||
7. One of the parties initiates (``Initiator``) an upgrade of state objects referring to contract X, to a new state object referring to contract Y.
|
||||
|
||||
8. A proposed transaction ``Proposal``, taking in the old state and outputting the reissued version, is created and signed with the node's private key.
|
||||
|
||||
9. The node ``Instigator`` sends the proposed transaction, along with details of the new contract upgrade path it's proposing, to all participants of the state object.
|
||||
9. The ``Initiator`` node sends the proposed transaction, along with details of the new contract upgrade path its proposing, to all participants of the state object.
|
||||
|
||||
10. Each counterparty ``Acceptor`` verifies the proposal, signs or rejects the state reissuance accordingly, and sends a signature or rejection notification back to the initiating node.
|
||||
|
||||
@ -48,32 +46,38 @@ Authorising upgrade
|
||||
-------------------
|
||||
|
||||
Each of the participants in the upgrading contract will have to instruct their node that they are willing to upgrade the state object before the upgrade.
|
||||
Currently the vault service is used to manage the authorisation records. The administrator can use RPC to perform such instructions.
|
||||
The ``ContractUpgradeFlow`` is used to manage the authorisation records. The administrator can use RPC to trigger either an ``Authorise`` or ``Deauthorise`` flow.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow] 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.
|
||||
*/
|
||||
fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>)
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will remove the upgrade authorisation from the vault.
|
||||
*/
|
||||
fun deauthoriseContractUpgrade(state: StateAndRef<*>)
|
||||
|
||||
/**
|
||||
* 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?>()
|
||||
|
||||
/**
|
||||
* 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?>()
|
||||
|
||||
Proposing an upgrade
|
||||
--------------------
|
||||
|
||||
After all parties have registered the intention of upgrading the contract state, one of the contract participant can initiate the upgrade process by running the contract upgrade flow.
|
||||
The Instigator will create a new state and sent to each participant for signatures, each of the participants (Acceptor) will verify and sign the proposal and returns to the instigator.
|
||||
After all parties have registered the intention of upgrading the contract state, one of the contract participants can initiate the upgrade process by triggering the ``Initiator`` contract upgrade flow.
|
||||
The ``Initiator`` will create a new state and sent to each participant for signatures, each of the participants (Acceptor) will verify, sign the proposal and return to the initiator.
|
||||
The transaction will be notarised and persisted once every participant verified and signed the upgrade proposal.
|
||||
|
||||
Examples
|
||||
@ -81,7 +85,7 @@ Examples
|
||||
|
||||
Lets assume Bank A has entered into an agreement with Bank B, and the contract is translated into contract code ``DummyContract`` with state object ``DummyContractState``.
|
||||
|
||||
Few days after the exchange of contracts, the developer of the contract code discovered a bug/misrepresentation in the contract code.
|
||||
A few days after the exchange of contracts, the developer of the contract code discovered a bug/misrepresentation in the contract code.
|
||||
Bank A and Bank B decided to upgrade the contract to ``DummyContractV2``
|
||||
|
||||
1. Developer will create a new contract extending the ``UpgradedContract`` class, and a new state object ``DummyContractV2.State`` referencing the new contract.
|
||||
@ -99,7 +103,7 @@ Bank A and Bank B decided to upgrade the contract to ``DummyContractV2``
|
||||
|
||||
val rpcClient : CordaRPCClient = << Bank A's Corda RPC Client >>
|
||||
val rpcA = rpcClient.proxy()
|
||||
rpcA.authoriseContractUpgrade(<<StateAndRef of the contract state>>, DummyContractV2::class.java)
|
||||
rpcA.startFlow(ContractUpgradeFlow.Authorise(<<StateAndRef of the contract state>>, DummyContractV2::class.java))
|
||||
|
||||
3. Bank B now initiate the upgrade Flow, this will send a upgrade proposal to all contract participants.
|
||||
Each of the participants of the contract state will sign and return the contract state upgrade proposal once they have validated and agreed with the upgrade.
|
||||
@ -115,7 +119,7 @@ The upgraded transaction state will be recorded in every participant's node at t
|
||||
<<StateAndRef of the contract state>>,
|
||||
DummyContractV2::class.java)
|
||||
|
||||
.. note:: See ``ContractUpgradeFlowTest.2 parties contract upgrade using RPC`` for more detailed code example.
|
||||
.. note:: See ``ContractUpgradeFlowTest`` for more detailed code examples.
|
||||
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user