Example code for contract upgrade using RPC. (#237)

* Added missing out modifier to UpgradedContract class
* Added ContractUpgradeFlow.Instigator to whitelist in AbstractNode
* Added test for contract upgrade using RPC
This commit is contained in:
Patrick Kuo 2017-02-13 15:39:48 +00:00 committed by GitHub
parent 36052cbd63
commit 28e83d1e66
11 changed files with 175 additions and 38 deletions

View File

@ -12,7 +12,7 @@ val DUMMY_V2_PROGRAM_ID = DummyContractV2()
* Dummy contract state for testing of the upgrade process. * Dummy contract state for testing of the upgrade process.
*/ */
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> { class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
override val legacyContract = DUMMY_PROGRAM_ID override val legacyContract = DummyContract::class.java
data class State(val magicNumber: Int = 0, val owners: List<CompositeKey>) : ContractState { data class State(val magicNumber: Int = 0, val owners: List<CompositeKey>) : ContractState {
override val contract = DUMMY_V2_PROGRAM_ID override val contract = DUMMY_V2_PROGRAM_ID

View File

@ -398,7 +398,7 @@ interface NetCommand : CommandData {
} }
/** Indicates that this transaction replaces the inputs contract state to another contract state */ /** Indicates that this transaction replaces the inputs contract state to another contract state */
data class UpgradeCommand(val upgradedContractClass: Class<UpgradedContract<*, *>>) : CommandData data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */ /** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
data class AuthenticatedObject<out T : Any>( data class AuthenticatedObject<out T : Any>(
@ -456,7 +456,7 @@ interface Contract {
* @param NewState the upgraded contract state. * @param NewState the upgraded contract state.
*/ */
interface UpgradedContract<in OldState : ContractState, out NewState : ContractState> : Contract { interface UpgradedContract<in OldState : ContractState, out NewState : ContractState> : Contract {
val legacyContract: Contract val legacyContract: Class<out Contract>
/** /**
* Upgrade contract's state object to a new state object. * Upgrade contract's state object to a new state object.
* *

View File

@ -114,7 +114,7 @@ interface CordaRPCOps : RPCOps {
* Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass]. * 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]. * This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
*/ */
fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>) fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>)
/** /**
* Authorise a contract state upgrade. * Authorise a contract state upgrade.

View File

@ -165,7 +165,7 @@ interface VaultService {
/** Get contracts we would be willing to upgrade the suggested contract to. */ /** Get contracts we would be willing to upgrade the suggested contract to. */
// TODO: We need a better place to put business logic functions // TODO: We need a better place to put business logic functions
fun getAuthorisedContractUpgrade(ref: StateRef): Class<UpgradedContract<*, *>>? fun getAuthorisedContractUpgrade(ref: StateRef): Class<out UpgradedContract<*, *>>?
/** /**
* Authorise a contract state upgrade. * Authorise a contract state upgrade.
@ -173,7 +173,7 @@ interface VaultService {
* Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass]. * 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]. * This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
*/ */
fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>) fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>)
/** /**
* Authorise a contract state upgrade. * Authorise a contract state upgrade.

View File

@ -33,7 +33,7 @@ object ContractUpgradeFlow {
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *> val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
requireThat { requireThat {
"The signing keys include all participant keys" by keysThatSigned.containsAll(participants) "The signing keys include all participant keys" by keysThatSigned.containsAll(participants)
"Inputs state reference the legacy contract" by (input.contract.javaClass == upgradedContract.legacyContract.javaClass) "Inputs state reference the legacy contract" by (input.contract.javaClass == upgradedContract.legacyContract)
"Outputs state reference the upgraded contract" by (output.contract.javaClass == command.upgradedContractClass) "Outputs state reference the upgraded contract" by (output.contract.javaClass == command.upgradedContractClass)
"Output state must be an upgraded version of the input state" by (output == upgradedContract.upgrade(input)) "Output state must be an upgraded version of the input state" by (output == upgradedContract.upgrade(input))
} }
@ -41,17 +41,17 @@ object ContractUpgradeFlow {
private fun <OldState : ContractState, NewState : ContractState> assembleBareTx( private fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>, stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<UpgradedContract<OldState, NewState>> upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>
): TransactionBuilder { ): TransactionBuilder {
val contractUpgrade = upgradedContractClass.newInstance() val contractUpgrade = upgradedContractClass.newInstance()
return TransactionType.General.Builder(stateRef.state.notary) return TransactionType.General.Builder(stateRef.state.notary)
.withItems(stateRef, contractUpgrade.upgrade(stateRef.state.data), Command(UpgradeCommand(contractUpgrade.javaClass), stateRef.state.data.participants)) .withItems(stateRef, contractUpgrade.upgrade(stateRef.state.data), Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants))
} }
class Instigator<OldState : ContractState, NewState : ContractState>( class Instigator<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>, originalState: StateAndRef<OldState>,
newContractClass: Class<UpgradedContract<OldState, NewState>> newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<UpgradedContract<OldState, NewState>>>(originalState, newContractClass) { ) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
override fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>> { override fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>> {
val stx = assembleBareTx(originalState, modification) val stx = assembleBareTx(originalState, modification)
@ -61,10 +61,10 @@ object ContractUpgradeFlow {
} }
} }
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<UpgradedContract<ContractState, *>>>(otherSide) { class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
@Suspendable @Suspendable
@Throws(StateReplacementException::class) @Throws(StateReplacementException::class)
override fun verifyProposal(proposal: Proposal<Class<UpgradedContract<ContractState, *>>>) { override fun verifyProposal(proposal: 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. // 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 stx = subFlow(FetchTransactionsFlow(setOf(proposal.stateRef.txhash), otherSide)).fromDisk.singleOrNull() val stx = subFlow(FetchTransactionsFlow(setOf(proposal.stateRef.txhash), otherSide)).fromDisk.singleOrNull()
requireNotNull(stx) { "We don't have a copy of the referenced state" } requireNotNull(stx) { "We don't have a copy of the referenced state" }

View File

@ -6,11 +6,16 @@ import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.utilities.Emoji import net.corda.core.utilities.Emoji
import net.corda.flows.CashIssueFlow import net.corda.flows.CashIssueFlow
import net.corda.flows.ContractUpgradeFlow import net.corda.flows.ContractUpgradeFlow
import net.corda.flows.FinalityFlow import net.corda.flows.FinalityFlow
import net.corda.node.internal.CordaRPCOpsImpl
import net.corda.node.services.User
import net.corda.node.services.messaging.CURRENT_RPC_USER
import net.corda.node.services.startFlowPermission
import net.corda.node.utilities.databaseTransaction import net.corda.node.utilities.databaseTransaction
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import org.junit.After import org.junit.After
@ -60,15 +65,15 @@ class ContractUpgradeFlowTest {
requireNotNull(btx) requireNotNull(btx)
// The request is expected to be rejected because party B haven't authorise the upgrade yet. // The request is expected to be rejected because party B haven't authorise the upgrade yet.
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Instigator<DummyContract.State, DummyContractV2.State>(atx!!.tx.outRef(0), DUMMY_V2_PROGRAM_ID.javaClass)).resultFuture val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Instigator(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith(ExecutionException::class) { rejectedFuture.get() } assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
// Party B authorise the contract state upgrade. // Party B authorise the contract state upgrade.
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DUMMY_V2_PROGRAM_ID.javaClass) b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
// Party A initiates contract upgrade flow, expected to succeed this time. // Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Instigator<DummyContract.State, DummyContractV2.State>(atx.tx.outRef(0), DUMMY_V2_PROGRAM_ID.javaClass)).resultFuture val resultFuture = a.services.startFlow(ContractUpgradeFlow.Instigator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
val result = resultFuture.get() val result = resultFuture.get()
@ -87,6 +92,63 @@ class ContractUpgradeFlowTest {
} }
} }
@Test
fun `2 parties contract upgrade using RPC`() {
// Create dummy contract.
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, a.info.legalIdentity.ref(1), b.info.legalIdentity.ref(1))
val stx = twoPartyDummyContract.signWith(a.services.legalIdentityKey)
.signWith(b.services.legalIdentityKey)
.toSignedTransaction()
a.services.startFlow(FinalityFlow(stx, setOf(a.info.legalIdentity, b.info.legalIdentity)))
mockNet.runNetwork()
val atx = databaseTransaction(a.database) { a.services.storageService.validatedTransactions.getTransaction(stx.id) }
val btx = databaseTransaction(b.database) { b.services.storageService.validatedTransactions.getTransaction(stx.id) }
requireNotNull(atx)
requireNotNull(btx)
// The request is expected to be rejected because party B haven't authorise the upgrade yet.
val rpcA = CordaRPCOpsImpl(a.services, a.smm, a.database)
val rpcB = CordaRPCOpsImpl(b.services, b.smm, b.database)
CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(
startFlowPermission<ContractUpgradeFlow.Instigator<*, *>>()
)))
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
atx!!.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
// Party B authorise the contract state upgrade.
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
// Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
atx.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
val result = resultFuture.get()
// Check results.
listOf(a, b).forEach {
val stx = databaseTransaction(a.database) { a.services.storageService.validatedTransactions.getTransaction(result.ref.txhash) }
requireNotNull(stx)
// Verify inputs.
val input = databaseTransaction(a.database) { a.services.storageService.validatedTransactions.getTransaction(stx!!.tx.inputs.single().txhash) }
requireNotNull(input)
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)
// Verify outputs.
assertTrue(stx!!.tx.outputs.single().data is DummyContractV2.State)
}
}
@Test @Test
fun `upgrade Cash to v2`() { fun `upgrade Cash to v2`() {
// Create some cash. // Create some cash.
@ -94,7 +156,7 @@ class ContractUpgradeFlowTest {
mockNet.runNetwork() mockNet.runNetwork()
val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0) val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0)
// Starts contract upgrade flow. // Starts contract upgrade flow.
a.services.startFlow(ContractUpgradeFlow.Instigator<Cash.State, CashV2.State>(stateAndRef, CashV2().javaClass)) a.services.startFlow(ContractUpgradeFlow.Instigator(stateAndRef, CashV2::class.java))
mockNet.runNetwork() mockNet.runNetwork()
// Get contract state form the vault. // Get contract state form the vault.
val state = databaseTransaction(a.database) { a.vault.currentVault.states } val state = databaseTransaction(a.database) { a.vault.currentVault.states }
@ -104,7 +166,7 @@ class ContractUpgradeFlowTest {
} }
class CashV2 : UpgradedContract<Cash.State, CashV2.State> { class CashV2 : UpgradedContract<Cash.State, CashV2.State> {
override val legacyContract = Cash() override val legacyContract = Cash::class.java
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<CompositeKey>) : FungibleAsset<Currency> { data class State(override val amount: Amount<Issued<Currency>>, val owners: List<CompositeKey>) : FungibleAsset<Currency> {
override val owner: CompositeKey = owners.first() override val owner: CompositeKey = owners.first()

View File

@ -1,3 +1,9 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Upgrading Contracts Upgrading Contracts
=================== ===================
@ -48,19 +54,18 @@ Currently the vault service is used to manage the authorisation records. The adm
.. sourcecode:: kotlin .. sourcecode:: kotlin
/** /**
* Authorise a contract state upgrade. * 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. * 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]. * 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]. * This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
*/ */
fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>) fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>)
/**
/** * Authorise a contract state upgrade.
* Authorise a contract state upgrade. * This will remove the upgrade authorisation from the vault.
* This will remove the upgrade authorisation from the vault. */
*/ fun deauthoriseContractUpgrade(state: StateAndRef<*>)
fun deauthoriseContractUpgrade(state: StateAndRef<*>)
@ -70,3 +75,72 @@ 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. 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. 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.
The transaction will be notarised and persisted once every participant verified and signed the upgrade proposal. The transaction will be notarised and persisted once every participant verified and signed the upgrade proposal.
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.
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.
.. container:: codeset
.. sourcecode:: kotlin
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
override val legacyContract = DummyContract::class.java
data class State(val magicNumber: Int = 0, val owners: List<CompositeKey>) : ContractState {
override val contract = DUMMY_V2_PROGRAM_ID
override val participants: List<CompositeKey> = owners
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
class Move : TypeOnlyCommandData(), Commands
}
override fun upgrade(state: DummyContract.State): DummyContractV2.State {
return DummyContractV2.State(state.magicNumber, state.participants)
}
override fun verify(tx: TransactionForContract) {
if (tx.commands.any { it.value is UpgradeCommand }) ContractUpgradeFlow.verify(tx)
// Other verifications.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("")
}
2. Bank A will instruct its node to accept the contract upgrade to ``DummyContractV2`` for the contract state.
.. container:: codeset
.. sourcecode:: kotlin
val rpcClient : CordaRPCClient = << Bank A's Corda RPC Client >>
val rpcA = rpcClient.proxy()
rpcA.authoriseContractUpgrade(<<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.
The upgraded transaction state will be recorded in every participant's node at the end of the flow.
.. container:: codeset
.. sourcecode:: kotlin
val rpcClient : CordaRPCClient = << Bank B's Corda RPC Client >>
val rpcB = rpcClient.proxy()
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
<<StateAndRef of the contract state>>,
DummyContractV2::class.java)
.. note:: See ``ContractUpgradeFlowTest.2 parties contract upgrade using RPC`` for more detailed code example.

View File

@ -87,6 +87,7 @@ Documentation Contents:
tutorial-contract tutorial-contract
tutorial-contract-clauses tutorial-contract-clauses
tutorial-test-dsl tutorial-test-dsl
contract-upgrade
tutorial-integration-testing tutorial-integration-testing
tutorial-clientrpc-api tutorial-clientrpc-api
tutorial-building-transactions tutorial-building-transactions
@ -105,7 +106,6 @@ Documentation Contents:
network-simulator network-simulator
clauses clauses
merkle-trees merkle-trees
contract-upgrade
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View File

@ -84,7 +84,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
CashExitFlow::class.java to setOf(Amount::class.java, PartyAndReference::class.java), CashExitFlow::class.java to setOf(Amount::class.java, PartyAndReference::class.java),
CashIssueFlow::class.java to setOf(Amount::class.java, OpaqueBytes::class.java, Party::class.java), CashIssueFlow::class.java to setOf(Amount::class.java, OpaqueBytes::class.java, Party::class.java),
CashPaymentFlow::class.java to setOf(Amount::class.java, Party::class.java), CashPaymentFlow::class.java to setOf(Amount::class.java, Party::class.java),
FinalityFlow::class.java to emptySet() FinalityFlow::class.java to emptySet(),
ContractUpgradeFlow.Instigator::class.java to emptySet()
) )
} }

View File

@ -103,7 +103,7 @@ class CordaRPCOpsImpl(
override fun attachmentExists(id: SecureHash) = services.storageService.attachments.openAttachment(id) != null override fun attachmentExists(id: SecureHash) = services.storageService.attachments.openAttachment(id) != null
override fun uploadAttachment(jar: InputStream) = services.storageService.attachments.importAttachment(jar) override fun uploadAttachment(jar: InputStream) = services.storageService.attachments.importAttachment(jar)
override fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>) = services.vaultService.authoriseContractUpgrade(state, upgradedContractClass) override fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>) = services.vaultService.authoriseContractUpgrade(state, upgradedContractClass)
override fun deauthoriseContractUpgrade(state: StateAndRef<*>) = services.vaultService.deauthoriseContractUpgrade(state) override fun deauthoriseContractUpgrade(state: StateAndRef<*>) = services.vaultService.deauthoriseContractUpgrade(state)
override fun currentNodeTime(): Instant = Instant.now(services.clock) override fun currentNodeTime(): Instant = Instant.now(services.clock)
@Suppress("OverridingDeprecatedMember", "DEPRECATION") @Suppress("OverridingDeprecatedMember", "DEPRECATION")

View File

@ -338,13 +338,13 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
} }
// TODO : Persists this in DB. // TODO : Persists this in DB.
private val authorisedUpgrade = mutableMapOf<StateRef, Class<UpgradedContract<*, *>>>() private val authorisedUpgrade = mutableMapOf<StateRef, Class<out UpgradedContract<*, *>>>()
override fun getAuthorisedContractUpgrade(ref: StateRef) = authorisedUpgrade[ref] override fun getAuthorisedContractUpgrade(ref: StateRef) = authorisedUpgrade[ref]
override fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>) { override fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>) {
val upgrade = upgradedContractClass.newInstance() val upgrade = upgradedContractClass.newInstance()
if (upgrade.legacyContract.javaClass != stateAndRef.state.data.contract.javaClass) { if (upgrade.legacyContract != stateAndRef.state.data.contract.javaClass) {
throw IllegalArgumentException("The contract state cannot be upgraded using provided UpgradedContract.") throw IllegalArgumentException("The contract state cannot be upgraded using provided UpgradedContract.")
} }
authorisedUpgrade.put(stateAndRef.ref, upgradedContractClass) authorisedUpgrade.put(stateAndRef.ref, upgradedContractClass)