Merge commit '88008b71f03a539749e32e5e76b2dc5ea98f897f' into chrisr3-44-merge

This commit is contained in:
Chris Rankin 2020-02-19 14:12:10 +00:00
commit 4c6f15c40c
18 changed files with 263 additions and 29 deletions

View File

@ -12,6 +12,9 @@ API: Contract Constraints
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`. .. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
.. note:: As of Corda |corda_version| the `minimumPlatformVersion` required to use these features is 4
(see :ref:`Network Parameters <network-parameters>` and :doc:`features-versions` for more details).
.. contents:: .. contents::
Reasons for Contract Constraints Reasons for Contract Constraints

View File

@ -0,0 +1,80 @@
Corda Features to Versions
==========================
New versions of Corda introduce new features. These fall into one of three categories which have subtle but important implications for
node owners, application developers and network operators.
The first set are changes that have no impact on application developers or the Corda network protocol. An example would be support for
a new HSM or database system, for example, and which are of interest only to a node's operator.
The second set are new or changed APIs, which are of interest to CorDapp developers. When a release of Corda ships such features, the
Platform Version of that node is incremented so that a CorDapp that relies on such a new or changed feature can detect this (eg to
prevent it from running on a node without the feature or to trigger an alternative optimised codepath if the feature is present). The
app developer should set the CorDapp's minimumPlatformVersion parameter to signal the minimum Platform Version against which the app
can run or has been tested. If the application has also been tested against a greater platform version and can exploit it if present,
the node can also set the targetPlatformVersion field.
The third set of changes are those which could affect the operation of a Corda network. Examples would include a change to the
serialisation format or flow/wire protocol, or introduction of a new transaction component. These are changes to the core data model and
these features have the property that it is not safe for any node or application to take advantage of until all nodes on the network
are capable of understanding them. Such features are thus only enabled in a node if the network to which it is connected has published
a minimumPlatformVersion in its network parameters that is greater than or equal to the Corda Platform Version that introduced the
feature. For example, Corda 4.0 nodes, which implement Corda Platform Version 4, can only take advantage of the Corda Reference States
feature when connected to a network with mPV 4.
If there is a Platform Version below which your application will not run or is not supported, then signal that with the application's
`CorDapp.mPV` field. This will prevent older nodes from running your app. Nodes which support newer Platform Versions may also use this
field to trigger code paths that emulate behaviours that were in force on that older Platform Version to maximise compatibility. However,
if you have tested your app against newer versions of Corda and know your node can take advantage of the new Platform Version behaviours
if present, you can signal this by using `CorDapp.targetPV` to declare the latest Platform Version against which the app has been tested
and is known to work. In this way, it is possible to ship CorDapps that can both run on all nodes supporting some minimum Platform Version
of Corda as well as opt in to newer features should they happen to be available on any given node.
.. list-table:: Corda Features
:header-rows: 1
* - Feature
- Corda Platform Version (PV)
- Min Network Platform Version (network mPV)
- Introduced in OS version
- Introduced in Enterprise version
* - Observer Nodes
- 2
- 2
- 2.0
- n/a
* - Corda Serialization Framework
- 3
- 3
- 3.0
- 3.0
* - Hash Constraints
- 1
- 1
- 1.0
- 1.0
* - Whitelist Constraints
- 3
- 3
- 3.0
- 3.0
* - Inline Finality Flow
- 4
- 3
- 4.0
- 4.0
* - Reference States
- 4
- 4
- 4.0
- 4.0
* - Signature Constraints
- 4
- 4
- 4.0
- 4.0
* - Underlying Support for Accounts
- 5
- 4
- 4.3
- 4.3

View File

@ -77,6 +77,8 @@ cluster generated like this can be sized for the maximum size you may need, and
More information can be found in :doc:`network-bootstrapper`. More information can be found in :doc:`network-bootstrapper`.
.. _network-parameters:
Network parameters Network parameters
------------------ ------------------
@ -132,6 +134,8 @@ The current set of network parameters:
Encountering an owned contract in a JAR that is not signed by the rightful owner is most likely a sign of malicious behaviour, and should be reported. Encountering an owned contract in a JAR that is not signed by the rightful owner is most likely a sign of malicious behaviour, and should be reported.
The transaction verification logic will throw an exception when this happens. The transaction verification logic will throw an exception when this happens.
.. note:: To determine which `minimumPlatformVersion` a zone must mandate in order to permit all the features of Corda |corda_version| see :doc:`features-versions`
More parameters will be added in future releases to regulate things like allowed port numbers, whether or not IPv6 More parameters will be added in future releases to regulate things like allowed port numbers, whether or not IPv6
connectivity is required for zone members, required cryptographic algorithms and roll-out schedules (e.g. for moving to post quantum cryptography), parameters related to SGX and so on. connectivity is required for zone members, required cryptographic algorithms and roll-out schedules (e.g. for moving to post quantum cryptography), parameters related to SGX and so on.

View File

@ -0,0 +1,38 @@
package net.corda.contracts.serialization.whitelist
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractState
import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.LedgerTransaction
class WhitelistContract : Contract {
companion object {
const val MAX_VALUE = 2000L
}
override fun verify(tx: LedgerTransaction) {
val states = tx.outputsOfType<State>()
require(states.isNotEmpty()) {
"Requires at least one data state"
}
states.forEach {
require(it.whitelistData in WhitelistData(0)..WhitelistData(MAX_VALUE)) {
"WhitelistData $it exceeds maximum value!"
}
}
}
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
class State(val owner: AbstractParty, val whitelistData: WhitelistData) : ContractState {
override val participants: List<AbstractParty> = listOf(owner)
@Override
override fun toString(): String {
return whitelistData.toString()
}
}
class Operate : CommandData
}

View File

@ -0,0 +1,15 @@
package net.corda.contracts.serialization.whitelist
import net.corda.core.serialization.SerializationWhitelist
data class WhitelistData(val value: Long) : Comparable<WhitelistData> {
override fun compareTo(other: WhitelistData): Int {
return value.compareTo(other.value)
}
override fun toString(): String = "$value things"
}
class Whitelist : SerializationWhitelist {
override val whitelist = listOf(WhitelistData::class.java)
}

View File

@ -6,11 +6,9 @@ import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData import net.corda.core.contracts.CommandData
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC @StartableByRPC
class SandboxAttachmentFlow(private val command: CommandData) : FlowLogic<SecureHash>() { class SandboxAttachmentFlow(private val command: CommandData) : FlowLogic<SecureHash>() {
@Suspendable @Suspendable

View File

@ -6,11 +6,9 @@ import net.corda.core.contracts.Command
import net.corda.core.contracts.TypeOnlyCommandData import net.corda.core.contracts.TypeOnlyCommandData
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC @StartableByRPC
class NonDeterministicFlow(private val trouble: TypeOnlyCommandData) : FlowLogic<SecureHash>() { class NonDeterministicFlow(private val trouble: TypeOnlyCommandData) : FlowLogic<SecureHash>() {
@Suspendable @Suspendable

View File

@ -6,12 +6,10 @@ import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData import net.corda.core.contracts.CommandData
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
@InitiatingFlow
@StartableByRPC @StartableByRPC
class DeterministicCryptoFlow( class DeterministicCryptoFlow(
private val command: CommandData, private val command: CommandData,

View File

@ -7,11 +7,9 @@ import net.corda.contracts.djvm.whitelist.WhitelistData
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC @StartableByRPC
class DeterministicWhitelistFlow(private val data: WhitelistData) : FlowLogic<SecureHash>() { class DeterministicWhitelistFlow(private val data: WhitelistData) : FlowLogic<SecureHash>() {
@Suspendable @Suspendable

View File

@ -7,11 +7,9 @@ import net.corda.contracts.fixup.dependent.DependentData
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC @StartableByRPC
class CordappFixupFlow(private val data: DependentData) : FlowLogic<SecureHash>() { class CordappFixupFlow(private val data: DependentData) : FlowLogic<SecureHash>() {
@Suspendable @Suspendable

View File

@ -7,11 +7,9 @@ import net.corda.contracts.serialization.custom.CustomSerializerContract.Purchas
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC @StartableByRPC
class CustomSerializerFlow( class CustomSerializerFlow(
private val purchase: Currantsy private val purchase: Currantsy

View File

@ -7,11 +7,9 @@ import net.corda.contracts.serialization.missing.MissingSerializerContract.Opera
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC @StartableByRPC
class MissingSerializerBuilderFlow(private val value: Long) : FlowLogic<SecureHash>() { class MissingSerializerBuilderFlow(private val value: Long) : FlowLogic<SecureHash>() {
@Suspendable @Suspendable

View File

@ -12,14 +12,12 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.internal.createComponentGroups import net.corda.core.internal.createComponentGroups
import net.corda.core.internal.requiredContractClassName import net.corda.core.internal.requiredContractClassName
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
@InitiatingFlow
@StartableByRPC @StartableByRPC
class MissingSerializerFlow(private val value: Long) : FlowLogic<SecureHash>() { class MissingSerializerFlow(private val value: Long) : FlowLogic<SecureHash>() {
@Suspendable @Suspendable

View File

@ -0,0 +1,26 @@
package net.corda.flows.serialization.whitelist
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.serialization.whitelist.WhitelistContract
import net.corda.contracts.serialization.whitelist.WhitelistContract.State
import net.corda.contracts.serialization.whitelist.WhitelistData
import net.corda.core.contracts.Command
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder
@StartableByRPC
class WhitelistFlow(private val data: WhitelistData) : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val stx = serviceHub.signInitialTransaction(
TransactionBuilder(notary)
.addOutputState(State(ourIdentity, data))
.addCommand(Command(WhitelistContract.Operate(), ourIdentity.owningKey))
)
stx.verify(serviceHub, checkSufficientSignatures = false)
return stx.id
}
}

View File

@ -0,0 +1,83 @@
package net.corda.node
import net.corda.client.rpc.CordaRPCClient
import net.corda.contracts.serialization.whitelist.WhitelistData
import net.corda.core.contracts.TransactionVerificationException.ContractRejection
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.flows.serialization.whitelist.WhitelistFlow
import net.corda.node.services.Permissions
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions.assertThat
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import kotlin.test.assertFailsWith
@RunWith(Parameterized::class)
@Suppress("FunctionName")
class ContractWithSerializationWhitelistTest(private val runInProcess: Boolean) {
companion object {
const val DATA = 123456L
@JvmField
val logger = loggerFor<ContractWithSerializationWhitelistTest>()
@JvmField
val contractCordapp = cordappWithPackages("net.corda.contracts.serialization.whitelist").signed()
@JvmField
val workflowCordapp = cordappWithPackages("net.corda.flows.serialization.whitelist").signed()
fun parametersFor(runInProcess: Boolean): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = runInProcess,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
cordappsForAllNodes = listOf(contractCordapp, workflowCordapp)
)
}
@Parameters
@JvmStatic
fun modes(): List<Array<Boolean>> = listOf(Array(1) { true }, Array(1) { false })
@BeforeClass
@JvmStatic
fun checkData() {
assertNotCordaSerializable<WhitelistData>()
}
}
@Test(timeout = 300_000)
fun `test serialization whitelist`() {
logger.info("RUN-IN-PROCESS=$runInProcess")
val user = User("u", "p", setOf(Permissions.all()))
driver(parametersFor(runInProcess = runInProcess)) {
val badData = WhitelistData(DATA)
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val ex = assertFailsWith<ContractRejection> {
CordaRPCClient(hostAndPort = alice.rpcAddress)
.start(user.username, user.password)
.use { client ->
client.proxy.startFlow(::WhitelistFlow, badData)
.returnValue
.getOrThrow()
}
}
assertThat(ex)
.hasMessageContaining("WhitelistData $badData exceeds maximum value!")
}
}
}

View File

@ -219,7 +219,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
// present in the CorDapp. // present in the CorDapp.
val result = scanResult.getClassesWithSuperclass(NotaryService::class) + val result = scanResult.getClassesWithSuperclass(NotaryService::class) +
scanResult.getClassesWithSuperclass(SinglePartyNotaryService::class) scanResult.getClassesWithSuperclass(SinglePartyNotaryService::class)
if(!result.isEmpty()) { if (result.isNotEmpty()) {
logger.info("Found notary service CorDapp implementations: " + result.joinToString(", ")) logger.info("Found notary service CorDapp implementations: " + result.joinToString(", "))
} }
return result.firstOrNull() return result.firstOrNull()

View File

@ -142,11 +142,7 @@ class DriverDSLImpl(
private lateinit var _notaries: CordaFuture<List<NotaryHandle>> private lateinit var _notaries: CordaFuture<List<NotaryHandle>>
override val notaryHandles: List<NotaryHandle> get() = _notaries.getOrThrow() override val notaryHandles: List<NotaryHandle> get() = _notaries.getOrThrow()
override val cordappsClassLoader: ClassLoader? = if (!startNodesInProcess) { override val cordappsClassLoader: URLClassLoader? = createCordappsClassLoader(cordappsForAllNodes)
createCordappsClassLoader(cordappsForAllNodes)
} else {
null
}
interface Waitable { interface Waitable {
@Throws(InterruptedException::class) @Throws(InterruptedException::class)
@ -195,14 +191,15 @@ class DriverDSLImpl(
} }
override fun shutdown() { override fun shutdown() {
if (waitForAllNodesToFinish) { cordappsClassLoader.use { _ ->
state.locked { if (waitForAllNodesToFinish) {
processes.forEach { it.waitFor() } state.locked {
processes.forEach { it.waitFor() }
}
} }
_shutdownManager?.shutdown()
_executorService?.shutdownNow()
} }
_shutdownManager?.shutdown()
_executorService?.shutdownNow()
(cordappsClassLoader as? AutoCloseable)?.close()
} }
private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> { private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
@ -992,7 +989,7 @@ class DriverDSLImpl(
return config return config
} }
private fun createCordappsClassLoader(cordapps: Collection<TestCordappInternal>?): ClassLoader? { private fun createCordappsClassLoader(cordapps: Collection<TestCordappInternal>?): URLClassLoader? {
if (cordapps == null || cordapps.isEmpty()) { if (cordapps == null || cordapps.isEmpty()) {
return null return null
} }

View File

@ -634,6 +634,10 @@ object InteractiveShell {
InputStreamSerializer.invokeContext = null InputStreamSerializer.invokeContext = null
InputStreamDeserializer.closeAll() InputStreamDeserializer.closeAll()
} }
if (cmd == "shutdown") {
out.println("Called 'shutdown' on the node.\nQuitting the shell now.").also { out.flush() }
onExit.invoke()
}
return result return result
} }