mirror of
https://github.com/corda/corda.git
synced 2024-12-20 13:33:12 +00:00
CORDA-2109 - Fix a bug that prevents consecutive multiparty contract upgrades (#4131)
The contract upgrade handler assumes that the state to be upgraded is created by a WireTransaction. This breaks the upgrade process if it was in fact issued by a ContractUpgradeWireTransactions or a NotaryChangeWireTransaction.
This commit is contained in:
parent
13843f1cae
commit
7e4a5413ac
@ -22,6 +22,7 @@ import net.corda.node.internal.StartedNode
|
|||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
import net.corda.node.services.Permissions.Companion.startFlow
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.contracts.DummyContractV2
|
import net.corda.testing.contracts.DummyContractV2
|
||||||
|
import net.corda.testing.contracts.DummyContractV3
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
@ -99,17 +100,28 @@ class ContractUpgradeFlowTest {
|
|||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
val result = resultFuture.resultFuture.getOrThrow()
|
val result = resultFuture.resultFuture.getOrThrow()
|
||||||
|
check<DummyContract.State, DummyContractV2.State>(aliceNode, result)
|
||||||
|
check<DummyContract.State, DummyContractV2.State>(bobNode, result)
|
||||||
|
|
||||||
fun check(node: StartedNode<MockNode>) {
|
// Check that the updated state can be further upgraded to V3
|
||||||
val upgradeTx = node.database.transaction {
|
// Party B authorise the contract state upgrade
|
||||||
val wtx = node.services.validatedTransactions.getTransaction(result.ref.txhash)
|
bobNode.services.startFlow(ContractUpgradeFlow.Authorise(result, DummyContractV3::class.java)).resultFuture.getOrThrow()
|
||||||
wtx!!.resolveContractUpgradeTransaction(node.services)
|
|
||||||
}
|
val resultFuture2 = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(result, DummyContractV3::class.java))
|
||||||
assertTrue(upgradeTx.inputs.single().state.data is DummyContract.State)
|
mockNet.runNetwork()
|
||||||
assertTrue(upgradeTx.outputs.single().data is DummyContractV2.State)
|
val result2 = resultFuture2.resultFuture.getOrThrow()
|
||||||
|
|
||||||
|
check<DummyContractV2.State, DummyContractV3.State>(aliceNode, result2)
|
||||||
|
check<DummyContractV2.State, DummyContractV3.State>(bobNode, result2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified OLD: ContractState, reified NEW: ContractState> check(node: StartedNode<MockNode>, state: StateAndRef<NEW>) {
|
||||||
|
val upgradeTx = node.database.transaction {
|
||||||
|
val wtx = node.services.validatedTransactions.getTransaction(state.ref.txhash)
|
||||||
|
wtx!!.resolveContractUpgradeTransaction(node.services)
|
||||||
}
|
}
|
||||||
check(aliceNode)
|
assertTrue(upgradeTx.inputs.single().state.data is OLD)
|
||||||
check(bobNode)
|
assertTrue(upgradeTx.outputs.single().data is NEW)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RPCDriverDSL.startProxy(node: StartedNode<MockNode>, user: User): CordaRPCOps {
|
private fun RPCDriverDSL.startProxy(node: StartedNode<MockNode>, user: User): CordaRPCOps {
|
||||||
|
@ -7,8 +7,8 @@ import net.corda.core.contracts.requireThat
|
|||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.ContractUpgradeUtils
|
import net.corda.core.internal.ContractUpgradeUtils
|
||||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
|
||||||
// TODO: We should have a whitelist of contracts we're willing to accept at all, and reject if the transaction
|
// TODO: We should have a whitelist of contracts we're willing to accept at all, and reject if the transaction
|
||||||
@ -54,9 +54,9 @@ class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementF
|
|||||||
// verify outputs matches the proposed upgrade.
|
// verify outputs matches the proposed upgrade.
|
||||||
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
|
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
|
||||||
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
|
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
|
||||||
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
val oldStateAndRef = ourSTX!!.resolveBaseTransaction(serviceHub).outRef<ContractState>(proposal.stateRef.index)
|
||||||
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref)
|
||||||
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
?: throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||||
val proposedTx = stx.coreTransaction as ContractUpgradeWireTransaction
|
val proposedTx = stx.coreTransaction as ContractUpgradeWireTransaction
|
||||||
val expectedTx = ContractUpgradeUtils.assembleUpgradeTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt, serviceHub)
|
val expectedTx = ContractUpgradeUtils.assembleUpgradeTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt, serviceHub)
|
||||||
requireThat {
|
requireThat {
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package net.corda.testing.contracts
|
||||||
|
|
||||||
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
|
|
||||||
|
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy contract state for testing of the upgrade process.
|
||||||
|
*/
|
||||||
|
class DummyContractV3 : UpgradedContractWithLegacyConstraint<DummyContractV2.State, DummyContractV3.State> {
|
||||||
|
companion object {
|
||||||
|
const val PROGRAM_ID: ContractClassName = "net.corda.testing.contracts.DummyContractV3"
|
||||||
|
}
|
||||||
|
|
||||||
|
override val legacyContract: String = DummyContractV2.PROGRAM_ID
|
||||||
|
override val legacyContractConstraint: AttachmentConstraint = AlwaysAcceptAttachmentConstraint
|
||||||
|
|
||||||
|
data class State(val magicNumber: Int = 0, val owners: List<AbstractParty>) : ContractState {
|
||||||
|
override val participants: List<AbstractParty> = owners
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Commands : CommandData {
|
||||||
|
class Create : TypeOnlyCommandData(), Commands
|
||||||
|
class Move : TypeOnlyCommandData(), Commands
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun upgrade(state: DummyContractV2.State): State {
|
||||||
|
return State(state.magicNumber, state.participants)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verify(tx: LedgerTransaction) {
|
||||||
|
// Other verifications.
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user