mirror of
https://github.com/corda/corda.git
synced 2024-12-22 14:22:28 +00:00
A temporary fix for contract upgrade transactions:
during LedgerTransaction verification run the right logic based on whether it contains the UpgradeCommand. Move ContractUpgradeFlowTest away from createSomeNodes() Remove assembleBareTx as it's not used
This commit is contained in:
parent
c0dd8d338e
commit
f8a43a8331
@ -199,9 +199,6 @@ interface MoveCommand : CommandData {
|
|||||||
val contract: Class<out Contract>?
|
val contract: Class<out Contract>?
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Indicates that this transaction replaces the inputs contract state to another contract state */
|
|
||||||
data class UpgradeCommand(val upgradedContractClass: ContractClassName) : CommandData
|
|
||||||
|
|
||||||
// DOCSTART 6
|
// DOCSTART 6
|
||||||
/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
|
/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
|
@ -3,10 +3,6 @@ package net.corda.core.flows
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.internal.ContractUpgradeUtils
|
import net.corda.core.internal.ContractUpgradeUtils
|
||||||
import net.corda.core.internal.uncheckedCast
|
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import java.security.PublicKey
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flow to be used for authorising and upgrading state objects of an old contract to a new contract.
|
* A flow to be used for authorising and upgrading state objects of an old contract to a new contract.
|
||||||
@ -17,29 +13,6 @@ import java.security.PublicKey
|
|||||||
* use the new updated state for future transactions.
|
* use the new updated state for future transactions.
|
||||||
*/
|
*/
|
||||||
object ContractUpgradeFlow {
|
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()
|
|
||||||
val upgradedContract: UpgradedContract<ContractState, *> = uncheckedCast(javaClass.classLoader.loadClass(command.upgradedContractClass).newInstance())
|
|
||||||
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.
|
* Authorise a contract state upgrade.
|
||||||
*
|
*
|
||||||
@ -53,7 +26,7 @@ object ContractUpgradeFlow {
|
|||||||
class Authorise(
|
class Authorise(
|
||||||
val stateAndRef: StateAndRef<*>,
|
val stateAndRef: StateAndRef<*>,
|
||||||
private val upgradedContractClass: Class<out UpgradedContract<*, *>>
|
private val upgradedContractClass: Class<out UpgradedContract<*, *>>
|
||||||
) : FlowLogic<Void?>() {
|
) : FlowLogic<Void?>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Void? {
|
override fun call(): Void? {
|
||||||
val upgrade = upgradedContractClass.newInstance()
|
val upgrade = upgradedContractClass.newInstance()
|
||||||
@ -89,23 +62,6 @@ object ContractUpgradeFlow {
|
|||||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
) : 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
|
@Suspendable
|
||||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||||
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())
|
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.core.contracts.CommandData
|
||||||
|
import net.corda.core.contracts.ContractClassName
|
||||||
|
|
||||||
|
/** Indicates that this transaction replaces the inputs contract state to another contract state */
|
||||||
|
data class UpgradeCommand(val upgradedContractClass: ContractClassName) : CommandData
|
@ -3,9 +3,11 @@ package net.corda.core.transactions
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.UpgradeCommand
|
||||||
import net.corda.core.internal.castIfPossible
|
import net.corda.core.internal.castIfPossible
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
|
||||||
@ -63,7 +65,13 @@ data class LedgerTransaction(
|
|||||||
@Throws(TransactionVerificationException::class)
|
@Throws(TransactionVerificationException::class)
|
||||||
fun verify() {
|
fun verify() {
|
||||||
verifyConstraints()
|
verifyConstraints()
|
||||||
verifyContracts()
|
// TODO: make contract upgrade transactions have a separate type
|
||||||
|
if (commands.any { it.value is UpgradeCommand }) {
|
||||||
|
verifyContractUpgrade()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
verifyContracts()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -153,6 +161,25 @@ data class LedgerTransaction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun verifyContractUpgrade() {
|
||||||
|
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||||
|
val input = inputs.single().state
|
||||||
|
val output = outputs.single()
|
||||||
|
val commandData = commandsOfType<UpgradeCommand>().single()
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
|
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
|
||||||
* can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement
|
* can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.core.contracts
|
package net.corda.core.contracts
|
||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.internal.UpgradeCommand
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.TestDependencyInjectionBase
|
import net.corda.testing.TestDependencyInjectionBase
|
||||||
|
@ -42,22 +42,15 @@ class ContractUpgradeFlowTest {
|
|||||||
fun setup() {
|
fun setup() {
|
||||||
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")
|
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")
|
||||||
mockNet = MockNetwork()
|
mockNet = MockNetwork()
|
||||||
val nodes = mockNet.createSomeNodes(notaryKeyPair = null) // prevent generation of notary override
|
val notaryNode = mockNet.createNotaryNode()
|
||||||
a = nodes.partyNodes[0]
|
a = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
|
||||||
b = nodes.partyNodes[1]
|
b = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||||
|
|
||||||
// Process registration
|
// Process registration
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
a.internals.ensureRegistered()
|
a.internals.ensureRegistered()
|
||||||
|
|
||||||
notary = a.services.getDefaultNotary()
|
notary = notaryNode.services.getDefaultNotary()
|
||||||
val nodeIdentity = nodes.notaryNode.info.legalIdentitiesAndCerts.single { it.party == notary }
|
|
||||||
a.database.transaction {
|
|
||||||
a.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
|
||||||
}
|
|
||||||
b.database.transaction {
|
|
||||||
b.services.identityService.verifyAndRegisterIdentity(nodeIdentity)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.node.services
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.UpgradeCommand
|
|
||||||
import net.corda.core.contracts.UpgradedContract
|
import net.corda.core.contracts.UpgradedContract
|
||||||
import net.corda.core.contracts.requireThat
|
import net.corda.core.contracts.requireThat
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
@ -64,9 +63,6 @@ class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementF
|
|||||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
|
"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)
|
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||||
}
|
}
|
||||||
ContractUpgradeFlow.verify(
|
proposedTx.toLedgerTransaction(serviceHub).verify()
|
||||||
oldStateAndRef.state,
|
|
||||||
expectedTx.outRef<ContractState>(0).state,
|
|
||||||
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,7 +391,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
|||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun createSomeNodes(numPartyNodes: Int = 2, nodeFactory: Factory<*> = defaultFactory, notaryKeyPair: KeyPair? = DUMMY_NOTARY_KEY): BasketOfNodes {
|
fun createSomeNodes(numPartyNodes: Int = 2, nodeFactory: Factory<*> = defaultFactory, notaryKeyPair: KeyPair? = DUMMY_NOTARY_KEY): BasketOfNodes {
|
||||||
require(nodes.isEmpty())
|
require(nodes.isEmpty())
|
||||||
val notaryServiceInfo = ServiceInfo(SimpleNotaryService.type)
|
val notaryServiceInfo = ServiceInfo(ValidatingNotaryService.type)
|
||||||
val notaryOverride = if (notaryKeyPair != null)
|
val notaryOverride = if (notaryKeyPair != null)
|
||||||
mapOf(Pair(notaryServiceInfo, notaryKeyPair))
|
mapOf(Pair(notaryServiceInfo, notaryKeyPair))
|
||||||
else
|
else
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package net.corda.testing.contracts
|
package net.corda.testing.contracts
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.flows.ContractUpgradeFlow
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.internal.UpgradeCommand
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -35,7 +35,6 @@ class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.St
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: LedgerTransaction) {
|
override fun verify(tx: LedgerTransaction) {
|
||||||
if (tx.commands.any { it.value is UpgradeCommand }) ContractUpgradeFlow.verify(tx)
|
|
||||||
// Other verifications.
|
// Other verifications.
|
||||||
}
|
}
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
|
Loading…
Reference in New Issue
Block a user