mirror of
https://github.com/corda/corda.git
synced 2025-01-18 10:46:38 +00:00
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:
parent
36052cbd63
commit
28e83d1e66
@ -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
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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" }
|
||||||
|
@ -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()
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user