CORDA-2178 Signature constraints minimum platform version checking (#4161)

* Minimum platform version checking for new signature constraints feature.

* Fix broken JUnit

* NP safety checking on network parameters.

* Warning and auto-downgrade of signed states that do not meet the minimum network platform version.
This commit is contained in:
josecoll 2018-11-09 12:27:28 +00:00 committed by GitHub
parent 99e9864975
commit 74c80aafd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 34 additions and 5 deletions

View File

@ -20,12 +20,15 @@ import org.slf4j.MDC
const val PLATFORM_VERSION = 4 const val PLATFORM_VERSION = 4
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) { fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
val currentMinPlatformVersion = networkParameters.minimumPlatformVersion checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)
if (currentMinPlatformVersion < requiredMinPlatformVersion) { }
fun checkMinimumPlatformVersion(minimumPlatformVersion: Int, requiredMinPlatformVersion: Int, feature: String) {
if (minimumPlatformVersion < requiredMinPlatformVersion) {
throw ZoneVersionTooLowException( throw ZoneVersionTooLowException(
"$feature requires all nodes on the Corda compatibility zone to be running at least platform version " + "$feature requires all nodes on the Corda compatibility zone to be running at least platform version " +
"$requiredMinPlatformVersion. The current zone is only enforcing a minimum platform version of " + "$requiredMinPlatformVersion. The current zone is only enforcing a minimum platform version of " +
"$currentMinPlatformVersion. Please contact your zone operator." "$minimumPlatformVersion. Please contact your zone operator."
) )
} }
} }

View File

@ -7,6 +7,7 @@ import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.castIfPossible import net.corda.core.internal.castIfPossible
import net.corda.core.internal.checkMinimumPlatformVersion
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -148,6 +149,8 @@ data class LedgerTransaction @JvmOverloads constructor(
val contractAttachment = uniqueAttachmentsForStateContract.first() val contractAttachment = uniqueAttachmentsForStateContract.first()
val constraintAttachment = AttachmentWithContext(contractAttachment, state.contract, networkParameters?.whitelistedContractImplementations) val constraintAttachment = AttachmentWithContext(contractAttachment, state.contract, networkParameters?.whitelistedContractImplementations)
if (state.constraint is SignatureAttachmentConstraint)
checkMinimumPlatformVersion(networkParameters?.minimumPlatformVersion ?: 1, 4, "Signature constraints")
if (!state.constraint.isSatisfiedBy(constraintAttachment)) { if (!state.constraint.isSatisfiedBy(constraintAttachment)) {
throw TransactionVerificationException.ContractConstraintRejection(id, state.contract) throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
} }

View File

@ -56,6 +56,10 @@ open class TransactionBuilder @JvmOverloads constructor(
private val inputsWithTransactionState = arrayListOf<TransactionState<ContractState>>() private val inputsWithTransactionState = arrayListOf<TransactionState<ContractState>>()
private val referencesWithTransactionState = arrayListOf<TransactionState<ContractState>>() private val referencesWithTransactionState = arrayListOf<TransactionState<ContractState>>()
companion object {
private val log = contextLogger()
}
/** /**
* Creates a copy of the builder. * Creates a copy of the builder.
*/ */
@ -165,7 +169,21 @@ open class TransactionBuilder @JvmOverloads constructor(
return when { return when {
attachmentSigners.isEmpty() -> HashAttachmentConstraint(attachmentId) attachmentSigners.isEmpty() -> HashAttachmentConstraint(attachmentId)
else -> makeSignatureAttachmentConstraint(attachmentSigners) else -> {
// Auto downgrade: signature constraints only available with a corda network minimum platform version of >= 4
if (services.networkParameters.minimumPlatformVersion < 4) {
log.warn("Signature constraints not available on network requiring a minimum platform version of ${services.networkParameters.minimumPlatformVersion}")
if (useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters)) {
log.warn("Reverting back to using whitelisted zone constraints")
WhitelistedByZoneAttachmentConstraint
}
else {
log.warn("Reverting back to using hash constraints")
HashAttachmentConstraint(attachmentId)
}
}
else makeSignatureAttachmentConstraint(attachmentSigners)
}
} }
} }

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
@ -40,7 +41,7 @@ class TransactionBuilderTest {
val cordappProvider = rigorousMock<CordappProvider>() val cordappProvider = rigorousMock<CordappProvider>()
doReturn(cordappProvider).whenever(services).cordappProvider doReturn(cordappProvider).whenever(services).cordappProvider
doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
doReturn(testNetworkParameters()).whenever(services).networkParameters doReturn(testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)).whenever(services).networkParameters
val attachmentStorage = rigorousMock<AttachmentStorage>() val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(services).attachments doReturn(attachmentStorage).whenever(services).attachments

View File

@ -88,6 +88,10 @@ and the smoothest to deploy: no restarts or contract upgrade transactions are ne
When CorDapp is build using :ref:`corda-gradle-plugin <cordapp_build_system_signing_cordapp_jar_ref>` the JAR is signed When CorDapp is build using :ref:`corda-gradle-plugin <cordapp_build_system_signing_cordapp_jar_ref>` the JAR is signed
by Corda development key by default, an external keystore can be configured or signing can be disabled. by Corda development key by default, an external keystore can be configured or signing can be disabled.
.. warning:: CorDapps can only use signature constraints when participating in a Corda network using a minimum platform version of 4.
An auto downgrade rule applies to signed CorDapps built and tested with Corda 4 but running on a Corda network of a lower version:
if the associated contract class is whitelisted in the network parameters then zone constraints are applied, otherwise hash constraints are used.
**Defaults.** The default constraint type is either a zone constraint, if the network parameters in effect when the **Defaults.** The default constraint type is either a zone constraint, if the network parameters in effect when the
transaction is built contain an entry for that contract class, or a hash constraint if not. transaction is built contain an entry for that contract class, or a hash constraint if not.