mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +00:00
Contract Upgrade API improvements + persistence (#1392)
* All Contract Upgrade functionality performed within a corresponding flow. Removed RPC API calls for contract upgrade authorisation / de-authorisation. Added persistence using AppendOnlyPersistentMap. * Changed to using a PersistentMap to ensure entries can be removed (was causing failing de-authorisation tests). Fixed all warnings. * Added mandatory @Suspendable annotations to flows. * Do not instantiate object unless overridden. * Updated changelog and CordaDocs. * Persistence simplification: only store upgrade contract class name (not serialized object) * Remove nullability from contract_class_name DB column.
This commit is contained in:
@ -9,6 +9,7 @@ import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.flows.ContractUpgradeFlow.Acceptor
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.*
|
||||
@ -18,14 +19,16 @@ import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.ServiceEntry
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.node.services.ContractUpgradeHandler
|
||||
import net.corda.node.services.NotaryChangeHandler
|
||||
import net.corda.node.services.NotifyTransactionHandler
|
||||
import net.corda.node.services.TransactionKeyHandler
|
||||
@ -38,11 +41,11 @@ import net.corda.node.services.identity.PersistentIdentityService
|
||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.node.services.messaging.sendRequest
|
||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.network.NetworkMapService.RegistrationRequest
|
||||
import net.corda.node.services.network.NetworkMapService.RegistrationResponse
|
||||
import net.corda.node.services.network.NodeRegistration
|
||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
||||
import net.corda.node.services.network.PersistentNetworkMapService
|
||||
import net.corda.node.services.persistence.DBCheckpointStorage
|
||||
import net.corda.node.services.persistence.DBTransactionMappingStorage
|
||||
@ -378,7 +381,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
.filter { it.isUserInvokable() } +
|
||||
// Add any core flows here
|
||||
listOf(
|
||||
ContractUpgradeFlow::class.java)
|
||||
ContractUpgradeFlow.Initiator::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -399,7 +402,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
private fun installCoreFlows() {
|
||||
installCoreFlow(BroadcastTransactionFlow::class, ::NotifyTransactionHandler)
|
||||
installCoreFlow(NotaryChangeFlow::class, ::NotaryChangeHandler)
|
||||
installCoreFlow(ContractUpgradeFlow::class, ::ContractUpgradeHandler)
|
||||
installCoreFlow(ContractUpgradeFlow.Initiator::class, ::Acceptor)
|
||||
installCoreFlow(TransactionKeyFlow::class, ::TransactionKeyHandler)
|
||||
}
|
||||
|
||||
|
@ -171,8 +171,6 @@ class CordaRPCOpsImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>) = services.contractUpgradeService.authoriseContractUpgrade(state, upgradedContractClass)
|
||||
override fun deauthoriseContractUpgrade(state: StateAndRef<*>) = services.contractUpgradeService.deauthoriseContractUpgrade(state)
|
||||
override fun currentNodeTime(): Instant = Instant.now(services.clock)
|
||||
|
||||
override fun waitUntilNetworkReady(): CordaFuture<Void?> {
|
||||
|
@ -49,31 +49,6 @@ class NotaryChangeHandler(otherSide: Party) : AbstractStateReplacementFlow.Accep
|
||||
}
|
||||
}
|
||||
|
||||
class ContractUpgradeHandler(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
|
||||
@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.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
|
||||
requireThat {
|
||||
"The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
|
||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade)
|
||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||
}
|
||||
ContractUpgradeFlow.verify(
|
||||
oldStateAndRef.state.data,
|
||||
expectedTx.outRef<ContractState>(0).state.data,
|
||||
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionKeyHandler(val otherSide: Party, val revocationEnabled: Boolean) : FlowLogic<Unit>() {
|
||||
constructor(otherSide: Party) : this(otherSide, false)
|
||||
companion object {
|
||||
|
@ -22,6 +22,7 @@ import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.node.services.transactions.RaftUniquenessProvider
|
||||
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
||||
import net.corda.node.services.vault.VaultSchemaV1
|
||||
|
||||
/**
|
||||
@ -54,7 +55,8 @@ class NodeSchemaService(customSchemas: Set<MappedSchema> = emptySet()) : SchemaS
|
||||
RaftUniquenessProvider.RaftState::class.java,
|
||||
BFTNonValidatingNotaryService.PersistedCommittedState::class.java,
|
||||
PersistentIdentityService.PersistentIdentity::class.java,
|
||||
PersistentIdentityService.PersistentIdentityNames::class.java
|
||||
PersistentIdentityService.PersistentIdentityNames::class.java,
|
||||
ContractUpgradeServiceImpl.DBContractUpgrade::class.java
|
||||
))
|
||||
|
||||
// Required schemas are those used by internal Corda services
|
||||
|
@ -1,26 +1,51 @@
|
||||
package net.corda.node.services.upgrade
|
||||
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.UpgradedContract
|
||||
import net.corda.core.node.services.ContractUpgradeService
|
||||
import net.corda.node.utilities.NODE_DATABASE_PREFIX
|
||||
import net.corda.node.utilities.PersistentMap
|
||||
import javax.persistence.*
|
||||
|
||||
class ContractUpgradeServiceImpl : ContractUpgradeService {
|
||||
|
||||
// TODO : Persist this in DB.
|
||||
private val authorisedUpgrade = mutableMapOf<StateRef, Class<out UpgradedContract<*, *>>>()
|
||||
@Entity
|
||||
@Table(name = "${NODE_DATABASE_PREFIX}contract_upgrades")
|
||||
class DBContractUpgrade(
|
||||
@Id
|
||||
@Column(name = "state_ref", length = 96)
|
||||
var stateRef: String = "",
|
||||
|
||||
override fun getAuthorisedContractUpgrade(ref: StateRef) = authorisedUpgrade[ref]
|
||||
/** refers to the UpgradedContract class name*/
|
||||
@Column(name = "contract_class_name")
|
||||
var upgradedContractClassName: String = ""
|
||||
)
|
||||
|
||||
override fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>) {
|
||||
val upgrade = upgradedContractClass.newInstance()
|
||||
if (upgrade.legacyContract != stateAndRef.state.data.contract.javaClass) {
|
||||
throw IllegalArgumentException("The contract state cannot be upgraded using provided UpgradedContract.")
|
||||
private companion object {
|
||||
fun createContractUpgradesMap(): PersistentMap<String, String, DBContractUpgrade, String> {
|
||||
return PersistentMap(
|
||||
toPersistentEntityKey = { it },
|
||||
fromPersistentEntity = { Pair(it.stateRef, it.upgradedContractClassName) },
|
||||
toPersistentEntity = { key: String, value: String ->
|
||||
DBContractUpgrade().apply {
|
||||
stateRef = key
|
||||
upgradedContractClassName = value
|
||||
}
|
||||
},
|
||||
persistentEntityClass = DBContractUpgrade::class.java
|
||||
)
|
||||
}
|
||||
authorisedUpgrade.put(stateAndRef.ref, upgradedContractClass)
|
||||
}
|
||||
|
||||
override fun deauthoriseContractUpgrade(stateAndRef: StateAndRef<*>) {
|
||||
authorisedUpgrade.remove(stateAndRef.ref)
|
||||
private val authorisedUpgrade = createContractUpgradesMap()
|
||||
|
||||
override fun getAuthorisedContractUpgrade(ref: StateRef) = authorisedUpgrade[ref.toString()]
|
||||
|
||||
override fun storeAuthorisedContractUpgrade(ref: StateRef, upgradedContractClass: Class<out UpgradedContract<*, *>>) {
|
||||
authorisedUpgrade.put(ref.toString(), upgradedContractClass.name)
|
||||
}
|
||||
|
||||
override fun removeAuthorisedContractUpgrade(ref: StateRef) {
|
||||
authorisedUpgrade.remove(ref.toString())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user