mirror of
https://github.com/corda/corda.git
synced 2024-12-21 05:53:23 +00:00
Cleaned up ContractUpgradeFlow API (#1591)
This commit is contained in:
parent
a11577840c
commit
11be5dd417
@ -2,9 +2,8 @@ package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.internal.ContractUpgradeUtils
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
@ -17,11 +16,37 @@ import java.security.PublicKey
|
||||
*/
|
||||
object ContractUpgradeFlow {
|
||||
|
||||
@JvmStatic
|
||||
fun verify(tx: LedgerTransaction) {
|
||||
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||
verify(tx.inputs.single().state,
|
||||
tx.outputs.single(),
|
||||
tx.commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun verify(input: TransactionState<ContractState>, output: TransactionState<ContractState>, commandData: Command<UpgradeCommand>) {
|
||||
val command = commandData.value
|
||||
val participantKeys: Set<PublicKey> = input.data.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = javaClass.classLoader.loadClass(command.upgradedContractClass).newInstance() as UpgradedContract<ContractState, *>
|
||||
requireThat {
|
||||
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
|
||||
"Inputs state reference the legacy contract" using (input.contract == upgradedContract.legacyContract)
|
||||
"Outputs state reference the upgraded contract" using (output.contract == command.upgradedContractClass)
|
||||
"Output state must be an upgraded version of the input state" using (output.data == upgradedContract.upgrade(input.data))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorise a contract state upgrade.
|
||||
* This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
|
||||
* Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using the [UpgradedContract] class.
|
||||
* This method will NOT initiate the upgrade process. To start the upgrade process, see [Initiator].
|
||||
*
|
||||
* This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor]
|
||||
* during contract upgrade process. Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using
|
||||
* the [UpgradedContract] class.
|
||||
*
|
||||
* This flow will NOT initiate the upgrade process. To start the upgrade process, see [Initiate].
|
||||
*/
|
||||
@StartableByRPC
|
||||
class Authorise(
|
||||
@ -45,9 +70,7 @@ object ContractUpgradeFlow {
|
||||
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
|
||||
*/
|
||||
@StartableByRPC
|
||||
class Deauthorise(
|
||||
val stateRef: StateRef
|
||||
) : FlowLogic< Void?>() {
|
||||
class Deauthorise(val stateRef: StateRef) : FlowLogic<Void?>() {
|
||||
@Suspendable
|
||||
override fun call(): Void? {
|
||||
serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
|
||||
@ -55,33 +78,19 @@ object ContractUpgradeFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This flow begins the contract upgrade process.
|
||||
*/
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class Initiator<OldState : ContractState, out NewState : ContractState>(
|
||||
class Initiate<OldState : ContractState, out NewState : ContractState>(
|
||||
originalState: StateAndRef<OldState>,
|
||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
||||
|
||||
companion object {
|
||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||
stateRef: StateAndRef<OldState>,
|
||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||
privacySalt: PrivacySalt
|
||||
): TransactionBuilder {
|
||||
val contractUpgrade = upgradedContractClass.newInstance()
|
||||
return TransactionBuilder(stateRef.state.notary)
|
||||
.withItems(
|
||||
stateRef,
|
||||
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
|
||||
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
|
||||
privacySalt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||
val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
|
||||
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())
|
||||
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
||||
// TODO: We need a much faster way of finding our key in the transaction
|
||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||
@ -89,57 +98,4 @@ object ContractUpgradeFlow {
|
||||
return AbstractStateReplacementFlow.UpgradeTx(stx)
|
||||
}
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
@InitiatedBy(ContractUpgradeFlow.Initiator::class)
|
||||
class Acceptor(otherSide: FlowSession) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun verify(tx: LedgerTransaction) {
|
||||
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||
verify(tx.inputs.single().state,
|
||||
tx.outputs.single(),
|
||||
tx.commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun verify(input: TransactionState<ContractState>, output: TransactionState<ContractState>, commandData: Command<UpgradeCommand>) {
|
||||
val command = commandData.value
|
||||
val participantKeys: Set<PublicKey> = input.data.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = javaClass.classLoader.loadClass(command.upgradedContractClass).newInstance() as UpgradedContract<ContractState, *>
|
||||
requireThat {
|
||||
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
|
||||
"Inputs state reference the legacy contract" using (input.contract == upgradedContract.legacyContract)
|
||||
"Outputs state reference the upgraded contract" using (output.contract == command.upgradedContractClass)
|
||||
"Output state must be an upgraded version of the input state" using (output.data == upgradedContract.upgrade(input.data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@Throws(StateReplacementException::class)
|
||||
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.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.
|
||||
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
|
||||
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
|
||||
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
||||
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
||||
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||
val proposedTx = stx.tx
|
||||
val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
||||
requireThat {
|
||||
"The instigator is one of the participants" using (initiatingSession.counterparty in oldStateAndRef.state.data.participants)
|
||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||
}
|
||||
ContractUpgradeFlow.Acceptor.verify(
|
||||
oldStateAndRef.state,
|
||||
expectedTx.outRef<ContractState>(0).state,
|
||||
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
object ContractUpgradeUtils {
|
||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
||||
stateRef: StateAndRef<OldState>,
|
||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||
privacySalt: PrivacySalt
|
||||
): TransactionBuilder {
|
||||
val contractUpgrade = upgradedContractClass.newInstance()
|
||||
return TransactionBuilder(stateRef.state.notary)
|
||||
.withItems(
|
||||
stateRef,
|
||||
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
|
||||
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
|
||||
privacySalt
|
||||
)
|
||||
}
|
||||
}
|
@ -81,7 +81,7 @@ class ContractUpgradeFlowTest {
|
||||
requireNotNull(btx)
|
||||
|
||||
// The request is expected to be rejected because party B hasn't authorised the upgrade yet.
|
||||
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Initiate(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
|
||||
|
||||
@ -90,7 +90,7 @@ class ContractUpgradeFlowTest {
|
||||
b.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).resultFuture.getOrThrow()
|
||||
|
||||
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
|
||||
val deauthorisedFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
val deauthorisedFuture = a.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() }
|
||||
|
||||
@ -98,7 +98,7 @@ class ContractUpgradeFlowTest {
|
||||
b.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow()
|
||||
|
||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
||||
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Initiator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
|
||||
val result = resultFuture.getOrThrow()
|
||||
@ -144,8 +144,7 @@ class ContractUpgradeFlowTest {
|
||||
|
||||
val user = rpcTestUser.copy(permissions = setOf(
|
||||
startFlowPermission<FinalityInvoker>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Initiator<*, *>>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Acceptor>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Initiate<*, *>>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Authorise>(),
|
||||
startFlowPermission<ContractUpgradeFlow.Deauthorise>()
|
||||
))
|
||||
@ -160,7 +159,7 @@ class ContractUpgradeFlowTest {
|
||||
requireNotNull(atx)
|
||||
requireNotNull(btx)
|
||||
|
||||
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
|
||||
atx!!.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
@ -175,7 +174,7 @@ class ContractUpgradeFlowTest {
|
||||
btx.tx.outRef<ContractState>(0).ref).returnValue
|
||||
|
||||
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
|
||||
val deauthorisedFuture = rpcA.startFlow( {stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
val deauthorisedFuture = rpcA.startFlow( {stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
|
||||
atx.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
@ -188,7 +187,7 @@ class ContractUpgradeFlowTest {
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
||||
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiator(stateAndRef, upgrade) },
|
||||
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
|
||||
atx.tx.outRef<DummyContract.State>(0),
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
@ -222,7 +221,7 @@ class ContractUpgradeFlowTest {
|
||||
val baseState = a.database.transaction { a.services.vaultQueryService.queryBy<ContractState>().states.single() }
|
||||
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
||||
// Starts contract upgrade flow.
|
||||
val upgradeResult = a.services.startFlow(ContractUpgradeFlow.Initiator(stateAndRef, CashV2::class.java)).resultFuture
|
||||
val upgradeResult = a.services.startFlow(ContractUpgradeFlow.Initiate(stateAndRef, CashV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
upgradeResult.getOrThrow()
|
||||
// Get contract state from the vault.
|
||||
|
@ -207,6 +207,8 @@ UNRELEASED
|
||||
* A new ``ComponentGroupEnum`` is added with the following enum items: ``INPUTS_GROUP``, ``OUTPUTS_GROUP``,
|
||||
``COMMANDS_GROUP``, ``ATTACHMENTS_GROUP``, ``NOTARY_GROUP``, ``TIMEWINDOW_GROUP``.
|
||||
|
||||
* ``ContractUpgradeFlow.Initiator`` has been renamed to ``ContractUpgradeFlow.Initiate``
|
||||
|
||||
Milestone 14
|
||||
------------
|
||||
|
||||
|
@ -8,7 +8,6 @@ import net.corda.confidential.SwapIdentitiesFlow
|
||||
import net.corda.confidential.SwapIdentitiesHandler
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.flows.ContractUpgradeFlow.Acceptor
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
@ -35,6 +34,7 @@ import net.corda.core.utilities.debug
|
||||
import net.corda.node.internal.classloading.requireAnnotation
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProvider
|
||||
import net.corda.node.services.ContractUpgradeHandler
|
||||
import net.corda.node.services.FinalityHandler
|
||||
import net.corda.node.services.NotaryChangeHandler
|
||||
import net.corda.node.services.api.*
|
||||
@ -372,7 +372,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
private fun installCoreFlows() {
|
||||
installCoreFlow(FinalityFlow::class, ::FinalityHandler)
|
||||
installCoreFlow(NotaryChangeFlow::class, ::NotaryChangeHandler)
|
||||
installCoreFlow(ContractUpgradeFlow.Initiator::class, ::Acceptor)
|
||||
installCoreFlow(ContractUpgradeFlow.Initiate::class, ::ContractUpgradeHandler)
|
||||
installCoreFlow(SwapIdentitiesFlow::class, ::SwapIdentitiesHandler)
|
||||
}
|
||||
|
||||
|
@ -54,8 +54,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
||||
/**
|
||||
* Creates a dev mode CordappLoader intended to only be used in test environments.
|
||||
*
|
||||
* @param scanPackage Resolves the JARs that contain scanPackage and use them as the source for
|
||||
* the classpath scanning.
|
||||
* @param scanPackages list of packages to scan.
|
||||
*/
|
||||
fun createDevMode(scanPackages: String): CordappLoader {
|
||||
val paths = scanPackages.split(",").flatMap { scanPackage ->
|
||||
@ -123,7 +122,11 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
||||
}
|
||||
|
||||
val found = scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() }
|
||||
val coreFlows = listOf(ContractUpgradeFlow.Initiator::class.java)
|
||||
val coreFlows = listOf(
|
||||
ContractUpgradeFlow.Initiate::class.java,
|
||||
ContractUpgradeFlow.Authorise::class.java,
|
||||
ContractUpgradeFlow.Deauthorise::class.java
|
||||
)
|
||||
return found + coreFlows
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.UpgradeCommand
|
||||
import net.corda.core.contracts.UpgradedContract
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.ContractUpgradeUtils
|
||||
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
|
||||
@ -41,3 +46,27 @@ class NotaryChangeHandler(otherSideSession: FlowSession) : AbstractStateReplacem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
|
||||
@Suspendable
|
||||
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.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.
|
||||
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
|
||||
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
|
||||
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
||||
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
||||
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||
val proposedTx = stx.tx
|
||||
val expectedTx = ContractUpgradeUtils.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
||||
requireThat {
|
||||
"The instigator is one of the participants" using (initiatingSession.counterparty in oldStateAndRef.state.data.participants)
|
||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||
}
|
||||
ContractUpgradeFlow.verify(
|
||||
oldStateAndRef.state,
|
||||
expectedTx.outRef<ContractState>(0).state,
|
||||
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class CordappLoaderTest {
|
||||
val actualCordapp = actual.first()
|
||||
assertThat(actualCordapp.contractClassNames).isEqualTo(listOf("net.corda.finance.contracts.isolated.AnotherDummyContract"))
|
||||
assertThat(actualCordapp.initiatedFlows).isEmpty()
|
||||
assertThat(actualCordapp.rpcFlows).isEqualTo(listOf(loader.appClassLoader.loadClass("net.corda.core.flows.ContractUpgradeFlow\$Initiator").asSubclass(FlowLogic::class.java)))
|
||||
assertThat(actualCordapp.rpcFlows).contains(loader.appClassLoader.loadClass("net.corda.core.flows.ContractUpgradeFlow\$Initiate").asSubclass(FlowLogic::class.java))
|
||||
assertThat(actualCordapp.services).isEmpty()
|
||||
assertThat(actualCordapp.plugins).hasSize(1)
|
||||
assertThat(actualCordapp.plugins.first().javaClass.name).isEqualTo("net.corda.finance.contracts.isolated.DummyPlugin")
|
||||
|
@ -15,7 +15,7 @@ const val DUMMY_V2_PROGRAM_ID: ContractClassName = "net.corda.testing.contracts.
|
||||
*/
|
||||
// DOCSTART 1
|
||||
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
|
||||
override val legacyContract = DummyContract::class.java.name
|
||||
override val legacyContract: String = DummyContract::class.java.name
|
||||
|
||||
data class State(val magicNumber: Int = 0, val owners: List<AbstractParty>) : ContractState {
|
||||
override val participants: List<AbstractParty> = owners
|
||||
@ -31,7 +31,7 @@ class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.St
|
||||
}
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
if (tx.commands.any { it.value is UpgradeCommand }) ContractUpgradeFlow.Acceptor.verify(tx)
|
||||
if (tx.commands.any { it.value is UpgradeCommand }) ContractUpgradeFlow.verify(tx)
|
||||
// Other verifications.
|
||||
}
|
||||
// DOCEND 1
|
||||
|
Loading…
Reference in New Issue
Block a user