CORDA-2577 Disable or delete the no-downgrade rule (#4741)

With (Contract JARs) rolling upgrades the downgrade rule cannot be effectively check as the platform can't tell the difference between a transaction that's downgrading because of an attack, vs a transaction that's downgrading because Alice has upgraded but Bob hasn't yet. During a rolling upgrade we would expect state versions to fluctuate up and down as data gets read/written by a mix of nodes. With the feature as implemented Alice will upgrade and start trading with Bob. Bob will be able to read and process the states Alice sent him, but the moment he tries to consume such a state he will fail. This will result in cascading flow deaths and a hung business network the moment an upgrade starts.
This commit is contained in:
szymonsztuka
2019-02-13 11:36:43 +00:00
committed by GitHub
parent 873e9a5442
commit 10e7c07c11
9 changed files with 10 additions and 50 deletions

View File

@ -262,15 +262,6 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
@KeepForDJVM
class OverlappingAttachmentsException(txId: SecureHash, path: String) : TransactionVerificationException(txId, "Multiple attachments define a file at $path.", null)
/**
* Thrown when a transaction appears to be trying to downgrade a state to an earlier version of the app that defines it.
* This could be an attempt to exploit a bug in the app, so we prevent it.
*/
@KeepForDJVM
class TransactionVerificationVersionException(txId: SecureHash, contractClassName: ContractClassName, inputVersion: String, outputVersion: String)
: TransactionVerificationException(txId, "No-Downgrade Rule has been breached for contract class $contractClassName. " +
"The output state contract version '$outputVersion' is lower than the version of the input state '$inputVersion'.", null)
/**
* Thrown to indicate that a contract attachment is not signed by the network-wide package owner. Please note that
* the [txId] will always be [SecureHash.zeroHash] because package ownership is an error with a particular attachment,

View File

@ -44,7 +44,6 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
// list, the contents of which need to be deserialized under the correct classloader.
checkNoNotaryChange()
checkEncumbrancesValid()
validateContractVersions()
validateStatesAgainstContract()
val hashToSignatureConstrainedContracts = verifyConstraintsValidity()
verifyConstraints(hashToSignatureConstrainedContracts)
@ -207,20 +206,6 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
}
}
/**
* Verify that contract class versions of output states are greater than or equal to the versions of the input states.
*/
private fun validateContractVersions() {
contractAttachmentsByContract.forEach { contractClassName, attachments ->
val outputVersion = attachments.signed?.version ?: attachments.unsigned?.version ?: CordappImpl.DEFAULT_CORDAPP_VERSION
inputVersions[contractClassName]?.let {
if (it > outputVersion) {
throw TransactionVerificationException.TransactionVerificationVersionException(ltx.id, contractClassName, "$it", "$outputVersion")
}
}
}
}
/**
* For all input and output [TransactionState]s, validates that the wrapped [ContractState] matches up with the
* wrapped [Contract], as declared by the [BelongsToContract] annotation on the [ContractState]'s class.

View File

@ -391,7 +391,7 @@ class ConstraintsPropagationTests {
recordTransactions(SignedTransaction(wireTransaction, sigs))
}
@Test
fun `Input state contract version is not compatible with lower version`() {
fun `Input state contract version may be incompatible with lower version`() {
ledgerServices.ledger(DUMMY_NOTARY) {
ledgerServices.recordTransaction(transaction {
attachment(Cash.PROGRAM_ID, SecureHash.allOnesHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "2"))
@ -404,7 +404,7 @@ class ConstraintsPropagationTests {
input("c1")
output(Cash.PROGRAM_ID, "c2", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), BOB_PARTY))
command(ALICE_PUBKEY, Cash.Commands.Move())
failsWith("No-Downgrade Rule has been breached for contract class net.corda.finance.contracts.asset.Cash. The output state contract version '1' is lower than the version of the input state '2'.")
verifies()
}
}
}
@ -448,7 +448,7 @@ class ConstraintsPropagationTests {
}
@Test
fun `All input states contract version must be lower that current contract version`() {
fun `Input states contract version may be lower that current contract version`() {
ledgerServices.ledger(DUMMY_NOTARY) {
ledgerServices.recordTransaction(transaction {
attachment(Cash.PROGRAM_ID, SecureHash.allOnesHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "1"))
@ -467,13 +467,13 @@ class ConstraintsPropagationTests {
input("c2")
output(Cash.PROGRAM_ID, "c3", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(2000.POUNDS `issued by` ALICE_PARTY.ref(1), BOB_PARTY))
command(ALICE_PUBKEY, Cash.Commands.Move())
failsWith("No-Downgrade Rule has been breached for contract class net.corda.finance.contracts.asset.Cash. The output state contract version '1' is lower than the version of the input state '2'.")
verifies()
}
}
}
@Test
fun `Input state with contract version can not be downgraded to no version`() {
fun `Input state with contract version can be downgraded to no version`() {
ledgerServices.ledger(DUMMY_NOTARY) {
ledgerServices.recordTransaction(transaction {
attachment(Cash.PROGRAM_ID, SecureHash.allOnesHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "2"))
@ -486,7 +486,7 @@ class ConstraintsPropagationTests {
input("c1")
output(Cash.PROGRAM_ID, "c2", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), BOB_PARTY))
command(ALICE_PUBKEY, Cash.Commands.Move())
failsWith("No-Downgrade Rule has been breached for contract class net.corda.finance.contracts.asset.Cash. The output state contract version '1' is lower than the version of the input state '2'.")
verifies()
}
}
}