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:
josecoll
2017-09-05 13:23:19 +01:00
committed by GitHub
parent 377ca95387
commit ebc9cacb53
14 changed files with 252 additions and 160 deletions

View File

@ -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)
}

View File

@ -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?> {

View File

@ -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 {

View File

@ -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

View File

@ -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())
}
}