mirror of
https://github.com/corda/corda.git
synced 2024-12-21 05:53:23 +00:00
CORDA-696 - Create separate transaction types for contract upgrade transactions (#2589)
* CORDA-986 and CORDA-985 CompositeKey and Signature verification performance fixes (#2467) * CORDA-696: Create separate transaction types for contract upgrade transactions. Add rationale around upgrade transactions Move contract upgrade transaction resolution logic into internal until it's stabilised. Throw a better exception when contract attachment not found Default legacy contract constraint to always accepting - needs to be changed to whitelist constraint before merging Introduce a new upgraded contract interface that allows specifying the legacy constraint. Remove StateLoader, make all tx resolution functions take in ServicesForResolution Contract upgrade transactions can handle whitelist by zone constraints When creating a contract upgrade transaction, make sure the attachment of the old cordapp gets attached when using hash constraints. Attachment lookup for a given contract class name only scans currently loaded cordapps, and we don't load old versions of cordapps. CORDA-696: Update upgrade docs
This commit is contained in:
parent
a483e7e8ce
commit
0edfef2409
@ -627,6 +627,9 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex
|
|||||||
@org.jetbrains.annotations.NotNull public abstract String getLegacyContract()
|
@org.jetbrains.annotations.NotNull public abstract String getLegacyContract()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState)
|
||||||
##
|
##
|
||||||
|
@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.UpgradedContractWithLegacyConstraint extends net.corda.core.contracts.UpgradedContract
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.AttachmentConstraint getLegacyContractConstraint()
|
||||||
|
##
|
||||||
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
|
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
|
||||||
public boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
|
public boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
|
||||||
public static final net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint INSTANCE
|
public static final net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint INSTANCE
|
||||||
@ -1894,16 +1897,12 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
|
|||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef)
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader
|
@net.corda.core.DoNotImplement public interface net.corda.core.node.ServicesForResolution
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NetworkParameters getNetworkParameters()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader
|
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
|
||||||
@org.jetbrains.annotations.NotNull public abstract Set loadStates(Set)
|
|
||||||
##
|
|
||||||
public final class net.corda.core.node.StatesToRecord extends java.lang.Enum
|
public final class net.corda.core.node.StatesToRecord extends java.lang.Enum
|
||||||
protected <init>(String, int)
|
protected <init>(String, int)
|
||||||
public static net.corda.core.node.StatesToRecord valueOf(String)
|
public static net.corda.core.node.StatesToRecord valueOf(String)
|
||||||
@ -2052,10 +2051,9 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l
|
|||||||
@org.jetbrains.annotations.NotNull public final java.time.Clock getClock()
|
@org.jetbrains.annotations.NotNull public final java.time.Clock getClock()
|
||||||
public final boolean isValid(net.corda.core.contracts.TimeWindow)
|
public final boolean isValid(net.corda.core.contracts.TimeWindow)
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage extends net.corda.core.node.StateLoader
|
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage
|
||||||
@org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
|
@org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
|
||||||
@org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates()
|
@org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService
|
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService
|
||||||
@ -3137,7 +3135,6 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
|
|||||||
@org.jetbrains.annotations.NotNull public List getOutputs()
|
@org.jetbrains.annotations.NotNull public List getOutputs()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.StateLoader, List)
|
|
||||||
public String toString()
|
public String toString()
|
||||||
##
|
##
|
||||||
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures
|
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures
|
||||||
@ -3160,13 +3157,11 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
|
|||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction getTx()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction getTx()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializedBytes getTxBits()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializedBytes getTxBits()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
public final boolean isNotaryChangeTransaction()
|
@kotlin.Deprecated public final boolean isNotaryChangeTransaction()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(Collection)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(Collection)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(net.corda.core.crypto.TransactionSignature)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(net.corda.core.crypto.TransactionSignature)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.BaseTransaction resolveBaseTransaction(net.corda.core.node.StateLoader)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolveNotaryChangeTransaction(net.corda.core.node.ServiceHub)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolveNotaryChangeTransaction(net.corda.core.node.ServiceHub)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolveNotaryChangeTransaction(net.corda.core.node.StateLoader)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionWithSignatures resolveTransactionWithSignatures(net.corda.core.node.ServicesForResolution)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionWithSignatures resolveTransactionWithSignatures(net.corda.core.node.ServiceHub)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub, boolean)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub, boolean)
|
||||||
@org.jetbrains.annotations.NotNull public String toString()
|
@org.jetbrains.annotations.NotNull public String toString()
|
||||||
@ -4075,7 +4070,7 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O
|
|||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withForcedID(Integer)
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withForcedID(Integer)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withLegalName(net.corda.core.identity.CordaX500Name)
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withLegalName(net.corda.core.identity.CordaX500Name)
|
||||||
##
|
##
|
||||||
public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.StateLoader, net.corda.core.node.ServiceHub
|
public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.ServiceHub
|
||||||
public <init>()
|
public <init>()
|
||||||
public <init>(List)
|
public <init>(List)
|
||||||
public <init>(List, net.corda.core.identity.CordaX500Name)
|
public <init>(List, net.corda.core.identity.CordaX500Name)
|
||||||
@ -4174,8 +4169,6 @@ public class net.corda.testing.node.MockTransactionStorage extends net.corda.cor
|
|||||||
public boolean addTransaction(net.corda.core.transactions.SignedTransaction)
|
public boolean addTransaction(net.corda.core.transactions.SignedTransaction)
|
||||||
@org.jetbrains.annotations.Nullable public net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
|
@org.jetbrains.annotations.Nullable public net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
|
||||||
@org.jetbrains.annotations.NotNull public rx.Observable getUpdates()
|
@org.jetbrains.annotations.NotNull public rx.Observable getUpdates()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
|
||||||
@org.jetbrains.annotations.NotNull public Set loadStates(Set)
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.messaging.DataFeed track()
|
@org.jetbrains.annotations.NotNull public net.corda.core.messaging.DataFeed track()
|
||||||
##
|
##
|
||||||
public final class net.corda.testing.node.NodeTestUtils extends java.lang.Object
|
public final class net.corda.testing.node.NodeTestUtils extends java.lang.Object
|
||||||
@ -4351,9 +4344,10 @@ public static final class net.corda.testing.contracts.DummyContract$SingleOwnerS
|
|||||||
@net.corda.core.DoNotImplement public static interface net.corda.testing.contracts.DummyContract$State extends net.corda.core.contracts.ContractState
|
@net.corda.core.DoNotImplement public static interface net.corda.testing.contracts.DummyContract$State extends net.corda.core.contracts.ContractState
|
||||||
public abstract int getMagicNumber()
|
public abstract int getMagicNumber()
|
||||||
##
|
##
|
||||||
public final class net.corda.testing.contracts.DummyContractV2 extends java.lang.Object implements net.corda.core.contracts.UpgradedContract
|
public final class net.corda.testing.contracts.DummyContractV2 extends java.lang.Object implements net.corda.core.contracts.UpgradedContractWithLegacyConstraint
|
||||||
public <init>()
|
public <init>()
|
||||||
@org.jetbrains.annotations.NotNull public String getLegacyContract()
|
@org.jetbrains.annotations.NotNull public String getLegacyContract()
|
||||||
|
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.AttachmentConstraint getLegacyContractConstraint()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.testing.contracts.DummyContractV2$State upgrade(net.corda.testing.contracts.DummyContract$State)
|
@org.jetbrains.annotations.NotNull public net.corda.testing.contracts.DummyContractV2$State upgrade(net.corda.testing.contracts.DummyContract$State)
|
||||||
public void verify(net.corda.core.transactions.LedgerTransaction)
|
public void verify(net.corda.core.transactions.LedgerTransaction)
|
||||||
public static final net.corda.testing.contracts.DummyContractV2$Companion Companion
|
public static final net.corda.testing.contracts.DummyContractV2$Companion Companion
|
||||||
|
@ -24,10 +24,7 @@ import net.corda.core.node.services.IdentityService
|
|||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.*
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.core.transactions.WireTransaction
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.base58ToByteArray
|
import net.corda.core.utilities.base58ToByteArray
|
||||||
import net.corda.core.utilities.base64ToByteArray
|
import net.corda.core.utilities.base64ToByteArray
|
||||||
|
@ -48,6 +48,7 @@ data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) {
|
|||||||
init {
|
init {
|
||||||
require(issuer.reference.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." }
|
require(issuer.reference.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = "$product issued by $issuer"
|
override fun toString() = "$product issued by $issuer"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,12 +249,21 @@ annotation class LegalProseReference(val uri: String)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface which can upgrade state objects issued by a contract to a new state object issued by a different contract.
|
* Interface which can upgrade state objects issued by a contract to a new state object issued by a different contract.
|
||||||
|
* The upgraded contract should specify the legacy contract class name, and provide an upgrade function that will convert
|
||||||
|
* legacy contract states into states defined by this contract.
|
||||||
|
*
|
||||||
|
* In addition to the legacy contract class name, you can also specify the legacy contract constraint by implementing
|
||||||
|
* [UpgradedContractWithLegacyConstraint] instead. Otherwise, the default [WhitelistedByZoneAttachmentConstraint] will
|
||||||
|
* be used for verifying the validity of an upgrade transaction.
|
||||||
*
|
*
|
||||||
* @param OldState the old contract state (can be [ContractState] or other common supertype if this supports upgrading
|
* @param OldState the old contract state (can be [ContractState] or other common supertype if this supports upgrading
|
||||||
* more than one state).
|
* more than one state).
|
||||||
* @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 {
|
||||||
|
/**
|
||||||
|
* Name of the contract this is an upgraded version of, used as part of verification of upgrade transactions.
|
||||||
|
*/
|
||||||
val legacyContract: ContractClassName
|
val legacyContract: ContractClassName
|
||||||
/**
|
/**
|
||||||
* Upgrade contract's state object to a new state object.
|
* Upgrade contract's state object to a new state object.
|
||||||
@ -264,6 +274,17 @@ interface UpgradedContract<in OldState : ContractState, out NewState : ContractS
|
|||||||
fun upgrade(state: OldState): NewState
|
fun upgrade(state: OldState): NewState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface allows specifying a custom legacy contract constraint for upgraded contracts. The default for [UpgradedContract]
|
||||||
|
* is [WhitelistedByZoneAttachmentConstraint].
|
||||||
|
*/
|
||||||
|
interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out NewState : ContractState> : UpgradedContract<OldState, NewState> {
|
||||||
|
/**
|
||||||
|
* A validator for the legacy (pre-upgrade) contract attachments on the transaction.
|
||||||
|
*/
|
||||||
|
val legacyContractConstraint: AttachmentConstraint
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot
|
* A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot
|
||||||
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
|
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
|
||||||
|
@ -2,7 +2,11 @@ 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.crypto.Crypto
|
||||||
|
import net.corda.core.crypto.SignableData
|
||||||
|
import net.corda.core.crypto.SignatureMetadata
|
||||||
import net.corda.core.internal.ContractUpgradeUtils
|
import net.corda.core.internal.ContractUpgradeUtils
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -38,7 +42,6 @@ object ContractUpgradeFlow {
|
|||||||
serviceHub.contractUpgradeService.storeAuthorisedContractUpgrade(stateAndRef.ref, upgradedContractClass)
|
serviceHub.contractUpgradeService.storeAuthorisedContractUpgrade(stateAndRef.ref, upgradedContractClass)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,11 +71,13 @@ object ContractUpgradeFlow {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||||
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())
|
val tx = ContractUpgradeUtils.assembleUpgradeTx(originalState, modification, PrivacySalt(), serviceHub)
|
||||||
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
||||||
// TODO: We need a much faster way of finding our key in the transaction
|
// TODO: We need a much faster way of finding our key in the transaction
|
||||||
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||||
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
val signableData = SignableData(tx.id, SignatureMetadata(serviceHub.myInfo.platformVersion, Crypto.findSignatureScheme(myKey).schemeNumberID))
|
||||||
|
val mySignature = serviceHub.keyManagementService.sign(signableData, myKey)
|
||||||
|
val stx = SignedTransaction(tx, listOf(mySignature))
|
||||||
return AbstractStateReplacementFlow.UpgradeTx(stx)
|
return AbstractStateReplacementFlow.UpgradeTx(stx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@ import net.corda.core.node.services.TrustedAuthorityNotaryService
|
|||||||
import net.corda.core.node.services.UniquenessProvider
|
import net.corda.core.node.services.UniquenessProvider
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.CoreTransaction
|
||||||
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -104,10 +106,11 @@ class NotaryFlow {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
|
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
|
||||||
val tx: CoreTransaction = if (stx.isNotaryChangeTransaction()) {
|
val ctx = stx.coreTransaction
|
||||||
stx.notaryChangeTx // Notary change transactions do not support filtering
|
val tx = when (ctx) {
|
||||||
} else {
|
is ContractUpgradeWireTransaction -> ctx.buildFilteredTransaction()
|
||||||
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
is WireTransaction -> ctx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
||||||
|
else -> ctx
|
||||||
}
|
}
|
||||||
return session.sendAndReceiveWithRetry(NotarisationPayload(tx, signature))
|
return session.sendAndReceiveWithRetry(NotarisationPayload(tx, signature))
|
||||||
}
|
}
|
||||||
@ -168,12 +171,10 @@ class NotaryFlow {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
abstract fun receiveAndVerifyTx(): TransactionParts
|
abstract fun receiveAndVerifyTx(): TransactionParts
|
||||||
|
|
||||||
// Check if transaction is intended to be signed by this notary.
|
/** Check if transaction is intended to be signed by this notary. */
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected fun checkNotary(notary: Party?) {
|
protected fun checkNotary(notary: Party?) {
|
||||||
// TODO This check implies that it's OK to use the node's main identity. Shouldn't it be just limited to the
|
if (notary?.owningKey != service.notaryIdentityKey) {
|
||||||
// notary identities?
|
|
||||||
if (notary == null || !serviceHub.myInfo.isLegalIdentity(notary)) {
|
|
||||||
throw NotaryException(NotaryError.WrongNotary)
|
throw NotaryException(NotaryError.WrongNotary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,38 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.node.ServicesForResolution
|
||||||
|
import net.corda.core.node.services.AttachmentId
|
||||||
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
|
|
||||||
object ContractUpgradeUtils {
|
object ContractUpgradeUtils {
|
||||||
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
fun <OldState : ContractState, NewState : ContractState> assembleUpgradeTx(
|
||||||
stateRef: StateAndRef<OldState>,
|
stateAndRef: StateAndRef<OldState>,
|
||||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
|
||||||
privacySalt: PrivacySalt
|
privacySalt: PrivacySalt,
|
||||||
): TransactionBuilder {
|
services: ServicesForResolution
|
||||||
val contractUpgrade = upgradedContractClass.newInstance()
|
): ContractUpgradeWireTransaction {
|
||||||
return TransactionBuilder(stateRef.state.notary)
|
require(stateAndRef.state.encumbrance == null) { "Upgrading an encumbered state is not yet supported" }
|
||||||
.withItems(
|
val legacyConstraint = stateAndRef.state.constraint
|
||||||
stateRef,
|
val legacyContractAttachmentId = when (legacyConstraint) {
|
||||||
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
|
is HashAttachmentConstraint -> legacyConstraint.attachmentId
|
||||||
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
|
else -> getContractAttachmentId(stateAndRef.state.contract, services)
|
||||||
privacySalt
|
}
|
||||||
)
|
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
|
||||||
|
|
||||||
|
val inputs = listOf(stateAndRef.ref)
|
||||||
|
return ContractUpgradeWireTransaction(
|
||||||
|
inputs,
|
||||||
|
stateAndRef.state.notary,
|
||||||
|
legacyContractAttachmentId,
|
||||||
|
upgradedContractClass.name,
|
||||||
|
upgradedContractAttachmentId,
|
||||||
|
privacySalt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getContractAttachmentId(name: ContractClassName, services: ServicesForResolution): AttachmentId {
|
||||||
|
return services.cordappProvider.getContractAttachmentID(name)
|
||||||
|
?: throw IllegalStateException("Attachment not found for contract: $name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.flows.FlowSession
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.exactAdd
|
import net.corda.core.utilities.exactAdd
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -157,13 +159,17 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
|
|||||||
* Returns a list of all the dependencies of the given transactions, deepest first i.e. the last downloaded comes
|
* Returns a list of all the dependencies of the given transactions, deepest first i.e. the last downloaded comes
|
||||||
* first in the returned list and thus doesn't have any unverified dependencies.
|
* first in the returned list and thus doesn't have any unverified dependencies.
|
||||||
*/
|
*/
|
||||||
|
// TODO: This could be done in parallel with other fetches for extra speed.
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun fetchMissingAttachments(downloads: List<SignedTransaction>) {
|
private fun fetchMissingAttachments(downloads: List<SignedTransaction>) {
|
||||||
// TODO: This could be done in parallel with other fetches for extra speed.
|
val attachments = downloads.map(SignedTransaction::coreTransaction).flatMap { tx ->
|
||||||
val wireTransactions = downloads.filterNot { it.isNotaryChangeTransaction() }.map { it.tx }
|
when (tx) {
|
||||||
val missingAttachments = wireTransactions.flatMap { wtx ->
|
is WireTransaction -> tx.attachments
|
||||||
wtx.attachments.filter { serviceHub.attachments.openAttachment(it) == null }
|
is ContractUpgradeWireTransaction -> listOf(tx.legacyContractAttachmentId, tx.upgradedContractAttachmentId)
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
val missingAttachments = attachments.filter { serviceHub.attachments.openAttachment(it) == null }
|
||||||
if (missingAttachments.isNotEmpty())
|
if (missingAttachments.isNotEmpty())
|
||||||
subFlow(FetchAttachmentsFlow(missingAttachments.toSet(), otherSide))
|
subFlow(FetchAttachmentsFlow(missingAttachments.toSet(), otherSide))
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
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
|
|
@ -18,39 +18,12 @@ import java.security.PublicKey
|
|||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of [ServiceHub].
|
|
||||||
*/
|
|
||||||
@DoNotImplement
|
|
||||||
interface StateLoader {
|
|
||||||
/**
|
|
||||||
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
|
|
||||||
*
|
|
||||||
* *WARNING* Do not use this method unless you really only want a single state - any batch loading should
|
|
||||||
* go through [loadStates] as repeatedly calling [loadState] can lead to repeat deserialsiation work and
|
|
||||||
* severe performance degradation.
|
|
||||||
*
|
|
||||||
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
|
||||||
*/
|
|
||||||
@Throws(TransactionResolutionException::class)
|
|
||||||
fun loadState(stateRef: StateRef): TransactionState<*>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a [Set] of [StateRef]'s loads the referenced transaction and looks up the specified output [ContractState].
|
|
||||||
*
|
|
||||||
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
|
||||||
*/
|
|
||||||
// TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load
|
|
||||||
// as the existing transaction store will become encrypted at some point
|
|
||||||
@Throws(TransactionResolutionException::class)
|
|
||||||
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subset of node services that are used for loading transactions from the wire into fully resolved, looked up
|
* Subset of node services that are used for loading transactions from the wire into fully resolved, looked up
|
||||||
* forms ready for verification.
|
* forms ready for verification.
|
||||||
*/
|
*/
|
||||||
interface ServicesForResolution : StateLoader {
|
@DoNotImplement
|
||||||
|
interface ServicesForResolution {
|
||||||
/**
|
/**
|
||||||
* An identity service maintains a directory of parties by their associated distinguished name/public keys and thus
|
* An identity service maintains a directory of parties by their associated distinguished name/public keys and thus
|
||||||
* supports lookup of a party given its key, or name. The service also manages the certificates linking confidential
|
* supports lookup of a party given its key, or name. The service also manages the certificates linking confidential
|
||||||
@ -66,6 +39,27 @@ interface ServicesForResolution : StateLoader {
|
|||||||
|
|
||||||
/** Returns the network parameters the node is operating under. */
|
/** Returns the network parameters the node is operating under. */
|
||||||
val networkParameters: NetworkParameters
|
val networkParameters: NetworkParameters
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
|
||||||
|
*
|
||||||
|
* *WARNING* Do not use this method unless you really only want a single state - any batch loading should
|
||||||
|
* go through [loadStates] as repeatedly calling [loadState] can lead to repeat deserialsiation work and
|
||||||
|
* severe performance degradation.
|
||||||
|
*
|
||||||
|
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
||||||
|
*/
|
||||||
|
@Throws(TransactionResolutionException::class)
|
||||||
|
fun loadState(stateRef: StateRef): TransactionState<*>
|
||||||
|
/**
|
||||||
|
* Given a [Set] of [StateRef]'s loads the referenced transaction and looks up the specified output [ContractState].
|
||||||
|
*
|
||||||
|
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
||||||
|
*/
|
||||||
|
// TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load
|
||||||
|
// as the existing transaction store will become encrypted at some point
|
||||||
|
@Throws(TransactionResolutionException::class)
|
||||||
|
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package net.corda.core.node.services
|
package net.corda.core.node.services
|
||||||
|
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.contracts.*
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.node.StateLoader
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
@ -12,27 +10,12 @@ import rx.Observable
|
|||||||
* Thread-safe storage of transactions.
|
* Thread-safe storage of transactions.
|
||||||
*/
|
*/
|
||||||
@DoNotImplement
|
@DoNotImplement
|
||||||
interface TransactionStorage : StateLoader {
|
interface TransactionStorage {
|
||||||
/**
|
/**
|
||||||
* Return the transaction with the given [id], or null if no such transaction exists.
|
* Return the transaction with the given [id], or null if no such transaction exists.
|
||||||
*/
|
*/
|
||||||
fun getTransaction(id: SecureHash): SignedTransaction?
|
fun getTransaction(id: SecureHash): SignedTransaction?
|
||||||
|
|
||||||
@Throws(TransactionResolutionException::class)
|
|
||||||
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
|
||||||
val stx = getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
|
||||||
return stx.resolveBaseTransaction(this).outputs[stateRef.index]
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(TransactionResolutionException::class)
|
|
||||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
|
|
||||||
return stateRefs.groupBy { it.txhash }.flatMap {
|
|
||||||
val stx = getTransaction(it.key) ?: throw TransactionResolutionException(it.key)
|
|
||||||
val baseTx = stx.resolveBaseTransaction(this)
|
|
||||||
it.value.map { StateAndRef(baseTx.outputs[it.index], it) }
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
|
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
|
||||||
* incorporate the update.
|
* incorporate the update.
|
||||||
|
@ -46,8 +46,8 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
|||||||
val produced: Set<StateAndRef<U>>,
|
val produced: Set<StateAndRef<U>>,
|
||||||
val flowId: UUID? = null,
|
val flowId: UUID? = null,
|
||||||
/**
|
/**
|
||||||
* Specifies the type of update, currently supported types are general and notary change. Notary
|
* Specifies the type of update, currently supported types are general and, contract upgrade and notary change.
|
||||||
* change transactions only modify the notary field on states, and potentially need to be handled
|
* Notary change transactions only modify the notary field on states, and potentially need to be handled
|
||||||
* differently.
|
* differently.
|
||||||
*/
|
*/
|
||||||
val type: UpdateType = UpdateType.GENERAL
|
val type: UpdateType = UpdateType.GENERAL
|
||||||
@ -97,11 +97,6 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
val NoUpdate = Update(emptySet(), emptySet(), type = Vault.UpdateType.GENERAL)
|
|
||||||
val NoNotaryUpdate = Vault.Update(emptySet(), emptySet(), type = Vault.UpdateType.NOTARY_CHANGE)
|
|
||||||
}
|
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
enum class StateStatus {
|
enum class StateStatus {
|
||||||
UNCONSUMED, CONSUMED, ALL
|
UNCONSUMED, CONSUMED, ALL
|
||||||
@ -109,7 +104,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
|||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
enum class UpdateType {
|
enum class UpdateType {
|
||||||
GENERAL, NOTARY_CHANGE
|
GENERAL, NOTARY_CHANGE, CONTRACT_UPGRADE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,6 +136,13 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
|||||||
val notary: AbstractParty?,
|
val notary: AbstractParty?,
|
||||||
val lockId: String?,
|
val lockId: String?,
|
||||||
val lockUpdateTime: Instant?)
|
val lockUpdateTime: Instant?)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Deprecated("No longer used. The vault does not emit empty updates")
|
||||||
|
val NoUpdate = Update(emptySet(), emptySet(), type = Vault.UpdateType.GENERAL)
|
||||||
|
@Deprecated("No longer used. The vault does not emit empty updates")
|
||||||
|
val NoNotaryUpdate = Vault.Update(emptySet(), emptySet(), type = Vault.UpdateType.NOTARY_CHANGE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
package net.corda.core.transactions
|
||||||
|
|
||||||
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.TransactionSignature
|
||||||
|
import net.corda.core.crypto.serializedHash
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.AttachmentWithContext
|
||||||
|
import net.corda.core.node.NetworkParameters
|
||||||
|
import net.corda.core.node.ServicesForResolution
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.utilities.toBase58String
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
// TODO: copy across encumbrances when performing contract upgrades
|
||||||
|
// TODO: check transaction size is within limits
|
||||||
|
|
||||||
|
/** A special transaction for upgrading the contract of a state. */
|
||||||
|
@CordaSerializable
|
||||||
|
data class ContractUpgradeWireTransaction(
|
||||||
|
override val inputs: List<StateRef>,
|
||||||
|
override val notary: Party,
|
||||||
|
val legacyContractAttachmentId: SecureHash,
|
||||||
|
val upgradeContractClassName: ContractClassName,
|
||||||
|
val upgradedContractAttachmentId: SecureHash,
|
||||||
|
val privacySalt: PrivacySalt = PrivacySalt()
|
||||||
|
) : CoreTransaction() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This transaction does not contain any output states, outputs can be obtained by resolving a
|
||||||
|
* [ContractUpgradeLedgerTransaction] – outputs will be calculated on demand by applying the contract
|
||||||
|
* upgrade operation to inputs.
|
||||||
|
*/
|
||||||
|
override val outputs: List<TransactionState<ContractState>>
|
||||||
|
get() = throw UnsupportedOperationException("ContractUpgradeWireTransaction does not contain output states, " +
|
||||||
|
"outputs can only be obtained from a resolved ContractUpgradeLedgerTransaction")
|
||||||
|
|
||||||
|
/** Hash of the list of components that are hidden in the [ContractUpgradeFilteredTransaction]. */
|
||||||
|
private val hiddenComponentHash: SecureHash
|
||||||
|
get() = serializedHash(listOf(legacyContractAttachmentId, upgradeContractClassName, privacySalt))
|
||||||
|
|
||||||
|
override val id: SecureHash by lazy { serializedHash(inputs + notary).hashConcat(hiddenComponentHash) }
|
||||||
|
|
||||||
|
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */
|
||||||
|
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
|
||||||
|
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
|
||||||
|
val legacyContractClassName = resolvedInputs.first().state.contract
|
||||||
|
val legacyContractAttachment = services.attachments.openAttachment(legacyContractAttachmentId)
|
||||||
|
?: throw AttachmentResolutionException(legacyContractAttachmentId)
|
||||||
|
val upgradedContractAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
|
||||||
|
?: throw AttachmentResolutionException(upgradedContractAttachmentId)
|
||||||
|
return ContractUpgradeLedgerTransaction(
|
||||||
|
resolvedInputs,
|
||||||
|
notary,
|
||||||
|
ContractAttachment(legacyContractAttachment, legacyContractClassName),
|
||||||
|
ContractAttachment(upgradedContractAttachment, upgradeContractClassName),
|
||||||
|
id,
|
||||||
|
privacySalt,
|
||||||
|
sigs,
|
||||||
|
services.networkParameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
|
||||||
|
return ContractUpgradeFilteredTransaction(inputs, notary, hiddenComponentHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filtered version of the [ContractUpgradeWireTransaction]. In comparison with a regular [FilteredTransaction], there
|
||||||
|
* is no flexibility on what parts of the transaction to reveal – the inputs and notary field are always visible and the
|
||||||
|
* rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary.
|
||||||
|
*
|
||||||
|
* @property inputs The inputs of this transaction.
|
||||||
|
* @property notary The notary for this transaction.
|
||||||
|
* @property rest Hash of the hidden components of the [ContractUpgradeWireTransaction].
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
data class ContractUpgradeFilteredTransaction(
|
||||||
|
override val inputs: List<StateRef>,
|
||||||
|
override val notary: Party,
|
||||||
|
val rest: SecureHash
|
||||||
|
) : CoreTransaction() {
|
||||||
|
override val id: SecureHash get() = serializedHash(inputs + notary).hashConcat(rest)
|
||||||
|
override val outputs: List<TransactionState<ContractState>> get() = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A contract upgrade transaction with fully resolved inputs and signatures. Contract upgrade transactions are separate
|
||||||
|
* to regular transactions because their validation logic is specialised; the original contract by definition cannot be
|
||||||
|
* aware of the upgraded contract (it was written after the original contract was developed), so its validation logic
|
||||||
|
* cannot succeed. Instead alternative verification logic is used which verifies that the outputs correspond to the
|
||||||
|
* inputs after upgrading.
|
||||||
|
*
|
||||||
|
* In contrast with a regular transaction, signatures are checked against the signers specified by input states'
|
||||||
|
* *participants* fields, so full resolution is needed for signature verification.
|
||||||
|
*/
|
||||||
|
data class ContractUpgradeLedgerTransaction(
|
||||||
|
override val inputs: List<StateAndRef<ContractState>>,
|
||||||
|
override val notary: Party,
|
||||||
|
val legacyContractAttachment: ContractAttachment,
|
||||||
|
val upgradedContractAttachment: ContractAttachment,
|
||||||
|
override val id: SecureHash,
|
||||||
|
val privacySalt: PrivacySalt,
|
||||||
|
override val sigs: List<TransactionSignature>,
|
||||||
|
private val networkParameters: NetworkParameters
|
||||||
|
) : FullTransaction(), TransactionWithSignatures {
|
||||||
|
private val upgradedContract: UpgradedContract<ContractState, *> = loadUpgradedContract()
|
||||||
|
|
||||||
|
init {
|
||||||
|
// TODO: relax this constraint once upgrading encumbered states is supported
|
||||||
|
check(inputs.all { it.state.contract == legacyContractAttachment.contract }) {
|
||||||
|
"All input states must point to the legacy contract"
|
||||||
|
}
|
||||||
|
check(inputs.all { it.state.constraint.isSatisfiedBy(legacyContractAttachment) }) {
|
||||||
|
"Legacy contract constraint does not satisfy the constraint of the input states"
|
||||||
|
}
|
||||||
|
verifyLegacyContractConstraint()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyLegacyContractConstraint() {
|
||||||
|
check(upgradedContract.legacyContract == legacyContractAttachment.contract) {
|
||||||
|
"Outputs' contract must be an upgraded version of the inputs' contract"
|
||||||
|
}
|
||||||
|
val attachmentWithContext = AttachmentWithContext(
|
||||||
|
legacyContractAttachment,
|
||||||
|
upgradedContract.legacyContract,
|
||||||
|
networkParameters.whitelistedContractImplementations
|
||||||
|
)
|
||||||
|
val constraintCheck = if (upgradedContract is UpgradedContractWithLegacyConstraint) {
|
||||||
|
upgradedContract.legacyContractConstraint.isSatisfiedBy(attachmentWithContext)
|
||||||
|
} else {
|
||||||
|
// If legacy constraint not specified, defaulting to WhitelistedByZoneAttachmentConstraint
|
||||||
|
WhitelistedByZoneAttachmentConstraint.isSatisfiedBy(attachmentWithContext)
|
||||||
|
}
|
||||||
|
check(constraintCheck) {
|
||||||
|
"Legacy contract does not satisfy the upgraded contract's constraint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outputs are computed by running the contract upgrade logic on input states. This is done eagerly so that the
|
||||||
|
* transaction is verified during construction.
|
||||||
|
*/
|
||||||
|
override val outputs: List<TransactionState<ContractState>> = inputs.map { input ->
|
||||||
|
// TODO: if there are encumbrance states in the inputs, just copy them across without modifying
|
||||||
|
val upgradedState = upgradedContract.upgrade(input.state.data)
|
||||||
|
val inputConstraint = input.state.constraint
|
||||||
|
val outputConstraint = when (inputConstraint) {
|
||||||
|
is HashAttachmentConstraint -> HashAttachmentConstraint(upgradedContractAttachment.id)
|
||||||
|
WhitelistedByZoneAttachmentConstraint -> WhitelistedByZoneAttachmentConstraint
|
||||||
|
else -> throw IllegalArgumentException("Unsupported input contract constraint $inputConstraint")
|
||||||
|
}
|
||||||
|
// TODO: re-map encumbrance pointers
|
||||||
|
input.state.copy(
|
||||||
|
data = upgradedState,
|
||||||
|
contract = upgradedContractAttachment.contract,
|
||||||
|
constraint = outputConstraint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The required signers are the set of all input states' participants. */
|
||||||
|
override val requiredSigningKeys: Set<PublicKey>
|
||||||
|
get() = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() + notary.owningKey
|
||||||
|
|
||||||
|
override fun getKeyDescriptions(keys: Set<PublicKey>): List<String> {
|
||||||
|
return keys.map { it.toBase58String() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: load contract from the CorDapp classloader
|
||||||
|
private fun loadUpgradedContract(): UpgradedContract<ContractState, *> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return this::class.java.classLoader
|
||||||
|
.loadClass(upgradedContractAttachment.contract)
|
||||||
|
.asSubclass(Contract::class.java)
|
||||||
|
.getConstructor()
|
||||||
|
.newInstance() as UpgradedContract<ContractState, *>
|
||||||
|
}
|
||||||
|
}
|
@ -4,13 +4,11 @@ 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.AttachmentWithContext
|
import net.corda.core.internal.AttachmentWithContext
|
||||||
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.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.Try
|
import net.corda.core.utilities.Try
|
||||||
import java.security.PublicKey
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
|
||||||
@ -79,12 +77,7 @@ data class LedgerTransaction @JvmOverloads constructor(
|
|||||||
@Throws(TransactionVerificationException::class)
|
@Throws(TransactionVerificationException::class)
|
||||||
fun verify() {
|
fun verify() {
|
||||||
verifyConstraints()
|
verifyConstraints()
|
||||||
// TODO: make contract upgrade transactions have a separate type
|
verifyContracts()
|
||||||
if (commands.any { it.value is UpgradeCommand }) {
|
|
||||||
verifyContractUpgrade()
|
|
||||||
} else {
|
|
||||||
verifyContracts()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,25 +178,6 @@ data class LedgerTransaction @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -429,3 +403,4 @@ data class LedgerTransaction @JvmOverloads constructor(
|
|||||||
privacySalt: PrivacySalt
|
privacySalt: PrivacySalt
|
||||||
) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null)
|
) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,11 @@ import net.corda.core.contracts.*
|
|||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.crypto.serializedHash
|
import net.corda.core.crypto.serializedHash
|
||||||
import net.corda.core.utilities.toBase58String
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.utilities.toBase58String
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +27,8 @@ data class NotaryChangeWireTransaction(
|
|||||||
* [NotaryChangeLedgerTransaction] and applying the notary modification to inputs.
|
* [NotaryChangeLedgerTransaction] and applying the notary modification to inputs.
|
||||||
*/
|
*/
|
||||||
override val outputs: List<TransactionState<ContractState>>
|
override val outputs: List<TransactionState<ContractState>>
|
||||||
get() = emptyList()
|
get() = throw UnsupportedOperationException("NotaryChangeWireTransaction does not contain output states, " +
|
||||||
|
"outputs can only be obtained from a resolved NotaryChangeLedgerTransaction")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
check(inputs.isNotEmpty()) { "A notary change transaction must have inputs" }
|
check(inputs.isNotEmpty()) { "A notary change transaction must have inputs" }
|
||||||
@ -40,11 +41,14 @@ data class NotaryChangeWireTransaction(
|
|||||||
*/
|
*/
|
||||||
override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) }
|
override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) }
|
||||||
|
|
||||||
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as StateLoader, sigs)
|
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
|
||||||
fun resolve(stateLoader: StateLoader, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
|
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>) : NotaryChangeLedgerTransaction {
|
||||||
val resolvedInputs = stateLoader.loadStates(inputs.toSet()).toList()
|
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
|
||||||
return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs)
|
return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
|
||||||
|
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as ServicesForResolution, sigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,7 @@ import net.corda.core.crypto.*
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
@ -48,19 +48,18 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
|
|
||||||
/** Cache the deserialized form of the transaction. This is useful when building a transaction or collecting signatures. */
|
/** Cache the deserialized form of the transaction. This is useful when building a transaction or collecting signatures. */
|
||||||
@Volatile
|
@Volatile
|
||||||
@Transient private var cachedTransaction: CoreTransaction? = null
|
@Transient
|
||||||
|
private var cachedTransaction: CoreTransaction? = null
|
||||||
/** Lazily calculated access to the deserialized/hashed transaction data. */
|
|
||||||
private val transaction: CoreTransaction get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this }
|
|
||||||
|
|
||||||
/** The id of the contained [WireTransaction]. */
|
/** The id of the contained [WireTransaction]. */
|
||||||
override val id: SecureHash get() = transaction.id
|
override val id: SecureHash get() = coreTransaction.id
|
||||||
|
|
||||||
/** Returns the contained [WireTransaction], or throws if this is a notary change transaction. */
|
/** Lazily calculated access to the deserialised/hashed transaction data. */
|
||||||
val tx: WireTransaction get() = transaction as WireTransaction
|
val coreTransaction: CoreTransaction
|
||||||
|
get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this }
|
||||||
|
|
||||||
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
/** Returns the contained [WireTransaction], or throws if this is a notary change or contract upgrade transaction. */
|
||||||
val notaryChangeTx: NotaryChangeWireTransaction get() = transaction as NotaryChangeWireTransaction
|
val tx: WireTransaction get() = coreTransaction as WireTransaction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to directly build a [FilteredTransaction] using provided filtering functions,
|
* Helper function to directly build a [FilteredTransaction] using provided filtering functions,
|
||||||
@ -69,9 +68,9 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
fun buildFilteredTransaction(filtering: Predicate<Any>) = tx.buildFilteredTransaction(filtering)
|
fun buildFilteredTransaction(filtering: Predicate<Any>) = tx.buildFilteredTransaction(filtering)
|
||||||
|
|
||||||
/** Helper to access the inputs of the contained transaction. */
|
/** Helper to access the inputs of the contained transaction. */
|
||||||
val inputs: List<StateRef> get() = transaction.inputs
|
val inputs: List<StateRef> get() = coreTransaction.inputs
|
||||||
/** Helper to access the notary of the contained transaction. */
|
/** Helper to access the notary of the contained transaction. */
|
||||||
val notary: Party? get() = transaction.notary
|
val notary: Party? get() = coreTransaction.notary
|
||||||
|
|
||||||
override val requiredSigningKeys: Set<PublicKey> get() = tx.requiredSigningKeys
|
override val requiredSigningKeys: Set<PublicKey> get() = tx.requiredSigningKeys
|
||||||
|
|
||||||
@ -162,13 +161,27 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
|
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
|
||||||
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
|
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
|
||||||
if (isNotaryChangeTransaction()) {
|
when (coreTransaction) {
|
||||||
verifyNotaryChangeTransaction(services, checkSufficientSignatures)
|
is NotaryChangeWireTransaction -> verifyNotaryChangeTransaction(services, checkSufficientSignatures)
|
||||||
} else {
|
is ContractUpgradeWireTransaction -> verifyContractUpgradeTransaction(services, checkSufficientSignatures)
|
||||||
verifyRegularTransaction(services, checkSufficientSignatures)
|
else -> verifyRegularTransaction(services, checkSufficientSignatures)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** No contract code is run when verifying notary change transactions, it is sufficient to check invariants during initialisation. */
|
||||||
|
private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
|
||||||
|
val ntx = resolveNotaryChangeTransaction(services)
|
||||||
|
if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
|
||||||
|
else checkSignaturesAreValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** No contract code is run when verifying contract upgrade transactions, it is sufficient to check invariants during initialisation. */
|
||||||
|
private fun verifyContractUpgradeTransaction(services: ServicesForResolution, checkSufficientSignatures: Boolean) {
|
||||||
|
val ctx = resolveContractUpgradeTransaction(services)
|
||||||
|
if (checkSufficientSignatures) ctx.verifyRequiredSignatures()
|
||||||
|
else checkSignaturesAreValid()
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
|
// TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
|
||||||
// from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
|
// from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
|
||||||
// objects from the TransactionState.
|
// objects from the TransactionState.
|
||||||
@ -178,37 +191,32 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
services.transactionVerifierService.verify(ltx).getOrThrow()
|
services.transactionVerifierService.verify(ltx).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
|
|
||||||
val ntx = resolveNotaryChangeTransaction(services)
|
|
||||||
if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
|
|
||||||
else checkSignaturesAreValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isNotaryChangeTransaction() = transaction is NotaryChangeWireTransaction
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the underlying base transaction and then returns it, handling any special case transactions such as
|
* Resolves the underlying base transaction and then returns it, handling any special case transactions such as
|
||||||
* [NotaryChangeWireTransaction].
|
* [NotaryChangeWireTransaction].
|
||||||
*/
|
*/
|
||||||
fun resolveBaseTransaction(services: StateLoader): BaseTransaction {
|
fun resolveBaseTransaction(servicesForResolution: ServicesForResolution): BaseTransaction {
|
||||||
return when (transaction) {
|
return when (coreTransaction) {
|
||||||
is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(services)
|
is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(servicesForResolution)
|
||||||
|
is ContractUpgradeWireTransaction -> resolveContractUpgradeTransaction(servicesForResolution)
|
||||||
is WireTransaction -> this.tx
|
is WireTransaction -> this.tx
|
||||||
is FilteredTransaction -> throw IllegalStateException("Persistence of filtered transactions is not supported.")
|
is FilteredTransaction -> throw IllegalStateException("Persistence of filtered transactions is not supported.")
|
||||||
else -> throw IllegalStateException("Unknown transaction type ${transaction::class.qualifiedName}")
|
else -> throw IllegalStateException("Unknown transaction type ${coreTransaction::class.qualifiedName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the underlying transaction with signatures and then returns it, handling any special case transactions
|
* Resolves the underlying transaction with signatures and then returns it, handling any special case transactions
|
||||||
* such as [NotaryChangeWireTransaction].
|
* such as [NotaryChangeWireTransaction].
|
||||||
*/
|
*/
|
||||||
fun resolveTransactionWithSignatures(services: ServiceHub): TransactionWithSignatures {
|
fun resolveTransactionWithSignatures(services: ServicesForResolution): TransactionWithSignatures {
|
||||||
return when (transaction) {
|
return when (coreTransaction) {
|
||||||
is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(services)
|
is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(services)
|
||||||
|
is ContractUpgradeWireTransaction -> resolveContractUpgradeTransaction(services)
|
||||||
is WireTransaction -> this
|
is WireTransaction -> this
|
||||||
is FilteredTransaction -> throw IllegalStateException("Persistence of filtered transactions is not supported.")
|
is FilteredTransaction -> throw IllegalStateException("Persistence of filtered transactions is not supported.")
|
||||||
else -> throw IllegalStateException("Unknown transaction type ${transaction::class.qualifiedName}")
|
else -> throw IllegalStateException("Unknown transaction type ${coreTransaction::class.qualifiedName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,12 +224,26 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
* If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
|
* If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
|
||||||
* [NotaryChangeLedgerTransaction] so the signatures can be verified.
|
* [NotaryChangeLedgerTransaction] so the signatures can be verified.
|
||||||
*/
|
*/
|
||||||
fun resolveNotaryChangeTransaction(services: ServiceHub) = resolveNotaryChangeTransaction(services as StateLoader)
|
fun resolveNotaryChangeTransaction(services: ServicesForResolution): NotaryChangeLedgerTransaction {
|
||||||
|
val ntx = coreTransaction as? NotaryChangeWireTransaction
|
||||||
|
?: throw IllegalStateException("Expected a ${NotaryChangeWireTransaction::class.simpleName} but found ${coreTransaction::class.simpleName}")
|
||||||
|
return ntx.resolve(services, sigs)
|
||||||
|
}
|
||||||
|
|
||||||
fun resolveNotaryChangeTransaction(stateLoader: StateLoader): NotaryChangeLedgerTransaction {
|
/**
|
||||||
val ntx = transaction as? NotaryChangeWireTransaction
|
* If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
|
||||||
?: throw IllegalStateException("Expected a ${NotaryChangeWireTransaction::class.simpleName} but found ${transaction::class.simpleName}")
|
* [NotaryChangeLedgerTransaction] so the signatures can be verified.
|
||||||
return ntx.resolve(stateLoader, sigs)
|
*/
|
||||||
|
fun resolveNotaryChangeTransaction(services: ServiceHub) = resolveNotaryChangeTransaction(services as ServicesForResolution)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If [coreTransaction] is a [ContractUpgradeWireTransaction], loads the input states and resolves it to a
|
||||||
|
* [ContractUpgradeLedgerTransaction] so the signatures can be verified.
|
||||||
|
*/
|
||||||
|
fun resolveContractUpgradeTransaction(services: ServicesForResolution): ContractUpgradeLedgerTransaction {
|
||||||
|
val ctx = coreTransaction as? ContractUpgradeWireTransaction
|
||||||
|
?: throw IllegalStateException("Expected a ${ContractUpgradeWireTransaction::class.simpleName} but found ${coreTransaction::class.simpleName}")
|
||||||
|
return ctx.resolve(services, sigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
|
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
|
||||||
@ -234,4 +256,14 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class SignaturesMissingException(val missing: Set<PublicKey>, val descriptions: List<String>, override val id: SecureHash)
|
class SignaturesMissingException(val missing: Set<PublicKey>, val descriptions: List<String>, override val id: SecureHash)
|
||||||
: NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id))
|
: NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id))
|
||||||
|
|
||||||
|
//region Deprecated
|
||||||
|
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
||||||
|
@Deprecated("No replacement, this should not be used outside of Corda core")
|
||||||
|
val notaryChangeTx: NotaryChangeWireTransaction
|
||||||
|
get() = coreTransaction as NotaryChangeWireTransaction
|
||||||
|
|
||||||
|
@Deprecated("No replacement, this should not be used outside of Corda core")
|
||||||
|
fun isNotaryChangeTransaction() = this.coreTransaction is NotaryChangeWireTransaction
|
||||||
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
package net.corda.core.contracts
|
|
||||||
|
|
||||||
import com.nhaarman.mockito_kotlin.any
|
|
||||||
import com.nhaarman.mockito_kotlin.doReturn
|
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
|
||||||
import net.corda.core.cordapp.CordappProvider
|
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import net.corda.core.crypto.SecureHash.Companion.allOnesHash
|
|
||||||
import net.corda.core.internal.UpgradeCommand
|
|
||||||
import net.corda.core.node.ServicesForResolution
|
|
||||||
import net.corda.testing.contracts.DummyContract
|
|
||||||
import net.corda.testing.contracts.DummyContractV2
|
|
||||||
import net.corda.testing.core.ALICE_NAME
|
|
||||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
|
||||||
import net.corda.testing.core.TestIdentity
|
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the version 2 dummy contract, to cover ensuring upgrade transactions are built correctly.
|
|
||||||
*/
|
|
||||||
class DummyContractV2Tests {
|
|
||||||
private companion object {
|
|
||||||
val ALICE = TestIdentity(ALICE_NAME, 70).party
|
|
||||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
|
||||||
}
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `upgrade from v1`() {
|
|
||||||
val services = rigorousMock<ServicesForResolution>().also {
|
|
||||||
doReturn(rigorousMock<CordappProvider>().also {
|
|
||||||
doReturn(allOnesHash).whenever(it).getContractAttachmentID(any())
|
|
||||||
}).whenever(it).cordappProvider
|
|
||||||
}
|
|
||||||
val contractUpgrade = DummyContractV2()
|
|
||||||
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
|
|
||||||
val v1Ref = StateRef(SecureHash.randomSHA256(), 0)
|
|
||||||
val v1StateAndRef = StateAndRef(v1State, v1Ref)
|
|
||||||
val (tx, _) = DummyContractV2().generateUpgradeFromV1(services, v1StateAndRef)
|
|
||||||
|
|
||||||
assertEquals(v1Ref, tx.inputs.single())
|
|
||||||
|
|
||||||
val expectedOutput = TransactionState(contractUpgrade.upgrade(v1State.data), DummyContractV2.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
|
|
||||||
val actualOutput = tx.outputs.single()
|
|
||||||
assertEquals(expectedOutput, actualOutput)
|
|
||||||
|
|
||||||
val actualCommand = tx.commands.map { it.value }.single()
|
|
||||||
assertTrue((actualCommand as UpgradeCommand).upgradedContractClass == DummyContractV2::class.java.name)
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,6 +10,7 @@ import net.corda.core.messaging.startFlow
|
|||||||
import net.corda.core.node.services.queryBy
|
import net.corda.core.node.services.queryBy
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.finance.USD
|
import net.corda.finance.USD
|
||||||
@ -101,20 +102,12 @@ class ContractUpgradeFlowTest {
|
|||||||
val result = resultFuture.getOrThrow()
|
val result = resultFuture.getOrThrow()
|
||||||
|
|
||||||
fun check(node: StartedNode<MockNode>) {
|
fun check(node: StartedNode<MockNode>) {
|
||||||
val nodeStx = node.database.transaction {
|
val upgradeTx = node.database.transaction {
|
||||||
node.services.validatedTransactions.getTransaction(result.ref.txhash)
|
val wtx = node.services.validatedTransactions.getTransaction(result.ref.txhash)
|
||||||
|
wtx!!.resolveContractUpgradeTransaction(node.services)
|
||||||
}
|
}
|
||||||
requireNotNull(nodeStx)
|
assertTrue(upgradeTx.inputs.single().state.data is DummyContract.State)
|
||||||
|
assertTrue(upgradeTx.outputs.single().data is DummyContractV2.State)
|
||||||
// Verify inputs.
|
|
||||||
val input = node.database.transaction {
|
|
||||||
node.services.validatedTransactions.getTransaction(nodeStx!!.tx.inputs.single().txhash)
|
|
||||||
}
|
|
||||||
requireNotNull(input)
|
|
||||||
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)
|
|
||||||
|
|
||||||
// Verify outputs.
|
|
||||||
assertTrue(nodeStx!!.tx.outputs.single().data is DummyContractV2.State)
|
|
||||||
}
|
}
|
||||||
check(aliceNode)
|
check(aliceNode)
|
||||||
check(bobNode)
|
check(bobNode)
|
||||||
@ -192,16 +185,12 @@ class ContractUpgradeFlowTest {
|
|||||||
val result = resultFuture.getOrThrow()
|
val result = resultFuture.getOrThrow()
|
||||||
// Check results.
|
// Check results.
|
||||||
listOf(aliceNode, bobNode).forEach {
|
listOf(aliceNode, bobNode).forEach {
|
||||||
val signedTX = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(result.ref.txhash) }
|
val upgradeTx = aliceNode.database.transaction {
|
||||||
requireNotNull(signedTX)
|
val wtx = aliceNode.services.validatedTransactions.getTransaction(result.ref.txhash)
|
||||||
|
wtx!!.resolveContractUpgradeTransaction(aliceNode.services)
|
||||||
// Verify inputs.
|
}
|
||||||
val input = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(signedTX!!.tx.inputs.single().txhash) }
|
assertTrue(upgradeTx.inputs.single().state.data is DummyContract.State)
|
||||||
requireNotNull(input)
|
assertTrue(upgradeTx.outputs.single().data is DummyContractV2.State)
|
||||||
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)
|
|
||||||
|
|
||||||
// Verify outputs.
|
|
||||||
assertTrue(signedTX!!.tx.outputs.single().data is DummyContractV2.State)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,14 +211,34 @@ class ContractUpgradeFlowTest {
|
|||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
upgradeResult.getOrThrow()
|
upgradeResult.getOrThrow()
|
||||||
// Get contract state from the vault.
|
// Get contract state from the vault.
|
||||||
val firstState = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<ContractState>().states.single() }
|
val upgradedStateFromVault = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<CashV2.State>().states.single() }
|
||||||
assertTrue(firstState.state.data is CashV2.State, "Contract state is upgraded to the new version.")
|
assertEquals(Amount(1000000, USD).`issued by`(chosenIdentity.ref(1)), upgradedStateFromVault.state.data.amount, "Upgraded cash contain the correct amount.")
|
||||||
assertEquals(Amount(1000000, USD).`issued by`(chosenIdentity.ref(1)), (firstState.state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
|
assertEquals<Collection<AbstractParty>>(listOf(anonymisedRecipient), upgradedStateFromVault.state.data.owners, "Upgraded cash belongs to the right owner.")
|
||||||
assertEquals<Collection<AbstractParty>>(listOf(anonymisedRecipient), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
|
// Make sure the upgraded state can be spent
|
||||||
|
val movedState = upgradedStateFromVault.state.data.copy(amount = upgradedStateFromVault.state.data.amount.times(2))
|
||||||
|
val spendUpgradedTx = aliceNode.services.signInitialTransaction(
|
||||||
|
TransactionBuilder(notary)
|
||||||
|
.addInputState(upgradedStateFromVault)
|
||||||
|
.addOutputState(
|
||||||
|
upgradedStateFromVault.state.copy(data = movedState)
|
||||||
|
)
|
||||||
|
.addCommand(CashV2.Move(), alice.owningKey)
|
||||||
|
|
||||||
|
)
|
||||||
|
aliceNode.services.startFlow(FinalityFlow(spendUpgradedTx)).apply {
|
||||||
|
mockNet.runNetwork()
|
||||||
|
get()
|
||||||
|
}
|
||||||
|
val movedStateFromVault = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<CashV2.State>().states.single() }
|
||||||
|
assertEquals(movedState, movedStateFromVault.state.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
class CashV2 : UpgradedContract<Cash.State, CashV2.State> {
|
class CashV2 : UpgradedContractWithLegacyConstraint<Cash.State, CashV2.State> {
|
||||||
override val legacyContract = Cash.PROGRAM_ID
|
override val legacyContract = Cash.PROGRAM_ID
|
||||||
|
override val legacyContractConstraint: AttachmentConstraint
|
||||||
|
get() = AlwaysAcceptAttachmentConstraint
|
||||||
|
|
||||||
|
class Move : TypeOnlyCommandData()
|
||||||
|
|
||||||
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<AbstractParty>) : FungibleAsset<Currency> {
|
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<AbstractParty>) : FungibleAsset<Currency> {
|
||||||
override val owner: AbstractParty = owners.first()
|
override val owner: AbstractParty = owners.first()
|
||||||
|
@ -16,6 +16,7 @@ class VaultUpdateTests {
|
|||||||
private companion object {
|
private companion object {
|
||||||
val DUMMY_PROGRAM_ID = "net.corda.core.node.VaultUpdateTests.DummyContract"
|
val DUMMY_PROGRAM_ID = "net.corda.core.node.VaultUpdateTests.DummyContract"
|
||||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||||
|
val emptyUpdate = Vault.Update(emptySet(), emptySet(), type = Vault.UpdateType.GENERAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DummyContract : Contract {
|
object DummyContract : Contract {
|
||||||
@ -42,21 +43,21 @@ class VaultUpdateTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `nothing plus nothing is nothing`() {
|
fun `nothing plus nothing is nothing`() {
|
||||||
val before = Vault.NoUpdate
|
val before = emptyUpdate
|
||||||
val after = before + Vault.NoUpdate
|
val after = before + emptyUpdate
|
||||||
assertEquals(before, after)
|
assertEquals(before, after)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `something plus nothing is something`() {
|
fun `something plus nothing is something`() {
|
||||||
val before = Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
val before = Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||||
val after = before + Vault.NoUpdate
|
val after = before + emptyUpdate
|
||||||
assertEquals(before, after)
|
assertEquals(before, after)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `nothing plus something is something`() {
|
fun `nothing plus something is something`() {
|
||||||
val before = Vault.NoUpdate
|
val before = emptyUpdate
|
||||||
val after = before + Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
val after = before + Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||||
val expected = Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
val expected = Vault.Update<ContractState>(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
|
||||||
assertEquals(expected, after)
|
assertEquals(expected, after)
|
||||||
|
@ -320,6 +320,23 @@ upgraded contracts must implement the ``UpgradedContract`` interface. This inter
|
|||||||
The ``upgrade`` method describes how the old state type is upgraded to the new state type. When the state isn't being
|
The ``upgrade`` method describes how the old state type is upgraded to the new state type. When the state isn't being
|
||||||
upgraded, the same state type can be used for both the old and new state type parameters.
|
upgraded, the same state type can be used for both the old and new state type parameters.
|
||||||
|
|
||||||
|
By default this new contract will only be able to upgrade legacy states which are constrained by the zone whitelist (see :doc:`api-contract-constraints`).
|
||||||
|
If hash or other constraint types are used, the new contract should implement ``UpgradedContractWithLegacyConstraint``
|
||||||
|
instead, and specify the constraint explicitly:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out NewState : ContractState> : UpgradedContract<OldState, NewState> {
|
||||||
|
val legacyContractConstraint: AttachmentConstraint
|
||||||
|
}
|
||||||
|
|
||||||
|
For example, in case of hash constraints the hash of the legacy JAR file should be provided:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
override val legacyContractConstraint: AttachmentConstraint
|
||||||
|
get() = HashAttachmentConstraint(SecureHash.parse("E02BD2B9B010BBCE49C0D7C35BECEF2C79BEB2EE80D902B54CC9231418A4FA0C"))
|
||||||
|
|
||||||
Authorising the upgrade
|
Authorising the upgrade
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Once the new states and contracts are on the classpath for all the relevant nodes, the next step is for all nodes to
|
Once the new states and contracts are on the classpath for all the relevant nodes, the next step is for all nodes to
|
||||||
|
@ -22,6 +22,7 @@ import net.corda.core.serialization.MissingAttachmentsException
|
|||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
@ -127,6 +128,7 @@ object DefaultKryoCustomizer {
|
|||||||
|
|
||||||
register(java.lang.invoke.SerializedLambda::class.java)
|
register(java.lang.invoke.SerializedLambda::class.java)
|
||||||
register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer)
|
register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer)
|
||||||
|
register(ContractUpgradeWireTransaction::class.java, ContractUpgradeWireTransactionSerializer)
|
||||||
|
|
||||||
for (whitelistProvider in serializationWhitelists) {
|
for (whitelistProvider in serializationWhitelists) {
|
||||||
val types = whitelistProvider.whitelist
|
val types = whitelistProvider.whitelist
|
||||||
|
@ -8,9 +8,12 @@ import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
|
|||||||
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
||||||
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.PrivacySalt
|
import net.corda.core.contracts.PrivacySalt
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
|
import net.corda.core.contracts.TransactionState
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
@ -265,6 +268,29 @@ object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
object ContractUpgradeWireTransactionSerializer : Serializer<ContractUpgradeWireTransaction>() {
|
||||||
|
override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeWireTransaction) {
|
||||||
|
kryo.writeClassAndObject(output, obj.inputs)
|
||||||
|
kryo.writeClassAndObject(output, obj.notary)
|
||||||
|
kryo.writeClassAndObject(output, obj.legacyContractAttachmentId)
|
||||||
|
kryo.writeClassAndObject(output, obj.upgradeContractClassName)
|
||||||
|
kryo.writeClassAndObject(output, obj.upgradedContractAttachmentId)
|
||||||
|
kryo.writeClassAndObject(output, obj.privacySalt)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeWireTransaction>): ContractUpgradeWireTransaction {
|
||||||
|
val inputs: List<StateRef> = uncheckedCast(kryo.readClassAndObject(input))
|
||||||
|
val notary = kryo.readClassAndObject(input) as Party
|
||||||
|
val legacyContractAttachment = kryo.readClassAndObject(input) as SecureHash
|
||||||
|
val upgradeContractClassName = kryo.readClassAndObject(input) as String
|
||||||
|
val upgradedContractAttachment = kryo.readClassAndObject(input) as SecureHash
|
||||||
|
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
|
||||||
|
|
||||||
|
return ContractUpgradeWireTransaction(inputs, notary, legacyContractAttachment, upgradeContractClassName, upgradedContractAttachment, privacySalt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
object SignedTransactionSerializer : Serializer<SignedTransaction>() {
|
object SignedTransactionSerializer : Serializer<SignedTransaction>() {
|
||||||
override fun write(kryo: Kryo, output: Output, obj: SignedTransaction) {
|
override fun write(kryo: Kryo, output: Output, obj: SignedTransaction) {
|
||||||
|
@ -208,17 +208,24 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService)
|
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService)
|
||||||
val (keyPairs, nodeInfo) = initNodeInfo(networkMapCache, identity, identityKeyPair)
|
val (keyPairs, nodeInfo) = initNodeInfo(networkMapCache, identity, identityKeyPair)
|
||||||
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
||||||
|
val metrics = MetricRegistry()
|
||||||
val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes)
|
val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes)
|
||||||
|
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
||||||
|
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations)
|
||||||
|
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, transactionStorage)
|
||||||
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database)
|
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database)
|
||||||
val nodeServices = makeServices(
|
val nodeServices = makeServices(
|
||||||
keyPairs,
|
keyPairs,
|
||||||
schemaService,
|
schemaService,
|
||||||
transactionStorage,
|
transactionStorage,
|
||||||
|
metrics,
|
||||||
|
servicesForResolution,
|
||||||
database,
|
database,
|
||||||
nodeInfo,
|
nodeInfo,
|
||||||
identityService,
|
identityService,
|
||||||
networkMapCache,
|
networkMapCache,
|
||||||
nodeProperties,
|
nodeProperties,
|
||||||
|
cordappProvider,
|
||||||
networkParameters)
|
networkParameters)
|
||||||
val notaryService = makeNotaryService(nodeServices, database)
|
val notaryService = makeNotaryService(nodeServices, database)
|
||||||
val smm = makeStateMachineManager(database)
|
val smm = makeStateMachineManager(database)
|
||||||
@ -228,7 +235,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
platformClock,
|
platformClock,
|
||||||
database,
|
database,
|
||||||
flowStarter,
|
flowStarter,
|
||||||
transactionStorage,
|
servicesForResolution,
|
||||||
unfinishedSchedules = busyNodeLatch,
|
unfinishedSchedules = busyNodeLatch,
|
||||||
serverThread = serverThread,
|
serverThread = serverThread,
|
||||||
flowLogicRefFactory = flowLogicRefFactory,
|
flowLogicRefFactory = flowLogicRefFactory,
|
||||||
@ -547,16 +554,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
private fun makeServices(keyPairs: Set<KeyPair>,
|
private fun makeServices(keyPairs: Set<KeyPair>,
|
||||||
schemaService: SchemaService,
|
schemaService: SchemaService,
|
||||||
transactionStorage: WritableTransactionStorage,
|
transactionStorage: WritableTransactionStorage,
|
||||||
|
metrics: MetricRegistry,
|
||||||
|
servicesForResolution: ServicesForResolution,
|
||||||
database: CordaPersistence,
|
database: CordaPersistence,
|
||||||
nodeInfo: NodeInfo,
|
nodeInfo: NodeInfo,
|
||||||
identityService: IdentityServiceInternal,
|
identityService: IdentityServiceInternal,
|
||||||
networkMapCache: NetworkMapCacheInternal,
|
networkMapCache: NetworkMapCacheInternal,
|
||||||
nodeProperties: NodePropertiesStore,
|
nodeProperties: NodePropertiesStore,
|
||||||
|
cordappProvider: CordappProviderInternal,
|
||||||
networkParameters: NetworkParameters): MutableList<Any> {
|
networkParameters: NetworkParameters): MutableList<Any> {
|
||||||
checkpointStorage = DBCheckpointStorage()
|
checkpointStorage = DBCheckpointStorage()
|
||||||
val metrics = MetricRegistry()
|
|
||||||
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
|
||||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations)
|
|
||||||
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
||||||
_services = ServiceHubInternalImpl(
|
_services = ServiceHubInternalImpl(
|
||||||
identityService,
|
identityService,
|
||||||
@ -569,7 +577,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
nodeInfo,
|
nodeInfo,
|
||||||
networkMapCache,
|
networkMapCache,
|
||||||
nodeProperties,
|
nodeProperties,
|
||||||
networkParameters)
|
networkParameters,
|
||||||
|
servicesForResolution)
|
||||||
network = makeMessagingService(database, nodeInfo, nodeProperties, networkParameters)
|
network = makeMessagingService(database, nodeInfo, nodeProperties, networkParameters)
|
||||||
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
|
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
|
||||||
services.keyManagementService, services.identityService, platformClock,
|
services.keyManagementService, services.identityService, platformClock,
|
||||||
@ -760,8 +769,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
|
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
|
||||||
protected open fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
protected open fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
||||||
return NodeVaultService(platformClock, keyManagementService, stateLoader, hibernateConfig)
|
return NodeVaultService(platformClock, keyManagementService, services, hibernateConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load configured JVM agents */
|
/** Load configured JVM agents */
|
||||||
@ -794,13 +803,14 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
override val myInfo: NodeInfo,
|
override val myInfo: NodeInfo,
|
||||||
override val networkMapCache: NetworkMapCacheInternal,
|
override val networkMapCache: NetworkMapCacheInternal,
|
||||||
override val nodeProperties: NodePropertiesStore,
|
override val nodeProperties: NodePropertiesStore,
|
||||||
override val networkParameters: NetworkParameters
|
override val networkParameters: NetworkParameters,
|
||||||
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by validatedTransactions {
|
private val servicesForResolution: ServicesForResolution
|
||||||
|
) : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution {
|
||||||
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
|
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
|
||||||
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
|
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
|
||||||
override val auditService = DummyAuditService()
|
override val auditService = DummyAuditService()
|
||||||
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
|
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
|
||||||
override val vaultService by lazy { makeVaultService(keyManagementService, validatedTransactions, database.hibernateConfig) }
|
override val vaultService by lazy { makeVaultService(keyManagementService, servicesForResolution, database.hibernateConfig) }
|
||||||
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
|
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
|
||||||
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
|
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
|
||||||
override val networkService: MessagingService get() = network
|
override val networkService: MessagingService get() = network
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package net.corda.node.internal
|
||||||
|
|
||||||
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.cordapp.CordappProvider
|
||||||
|
import net.corda.core.node.NetworkParameters
|
||||||
|
import net.corda.core.node.ServicesForResolution
|
||||||
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
|
import net.corda.core.node.services.IdentityService
|
||||||
|
import net.corda.core.node.services.TransactionStorage
|
||||||
|
|
||||||
|
data class ServicesForResolutionImpl(
|
||||||
|
override val identityService: IdentityService,
|
||||||
|
override val attachments: AttachmentStorage,
|
||||||
|
override val cordappProvider: CordappProvider,
|
||||||
|
override val networkParameters: NetworkParameters,
|
||||||
|
private val validatedTransactions: TransactionStorage
|
||||||
|
) : ServicesForResolution {
|
||||||
|
@Throws(TransactionResolutionException::class)
|
||||||
|
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||||
|
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||||
|
return stx.resolveBaseTransaction(this).outputs[stateRef.index]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(TransactionResolutionException::class)
|
||||||
|
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
|
||||||
|
return stateRefs.groupBy { it.txhash }.map {
|
||||||
|
val stx = validatedTransactions.getTransaction(it.key) ?: throw TransactionResolutionException(it.key)
|
||||||
|
val baseTx = stx.resolveBaseTransaction(this)
|
||||||
|
it.value.map { StateAndRef(baseTx.outputs[it.index], it) }
|
||||||
|
}.flatMap { it }.toSet()
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
|||||||
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
||||||
import net.corda.core.contracts.Contract
|
import net.corda.core.contracts.Contract
|
||||||
import net.corda.core.contracts.UpgradedContract
|
import net.corda.core.contracts.UpgradedContract
|
||||||
|
import net.corda.core.contracts.UpgradedContractWithLegacyConstraint
|
||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
@ -241,7 +242,13 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findContractClassNames(scanResult: RestrictedScanResult): List<String> {
|
private fun findContractClassNames(scanResult: RestrictedScanResult): List<String> {
|
||||||
return (scanResult.getNamesOfClassesImplementing(Contract::class) + scanResult.getNamesOfClassesImplementing(UpgradedContract::class)).distinct()
|
return (scanResult.getNamesOfClassesImplementing(Contract::class) +
|
||||||
|
scanResult.getNamesOfClassesImplementing(UpgradedContract::class) +
|
||||||
|
// Even though UpgradedContractWithLegacyConstraint implements UpgradedContract
|
||||||
|
// we need to specify it separately. Otherwise, classes implementing UpgradedContractWithLegacyConstraint
|
||||||
|
// don't get picked up.
|
||||||
|
scanResult.getNamesOfClassesImplementing(UpgradedContractWithLegacyConstraint::class))
|
||||||
|
.distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findPlugins(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
private fun findPlugins(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.requireThat
|
|||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.ContractUpgradeUtils
|
import net.corda.core.internal.ContractUpgradeUtils
|
||||||
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
|
||||||
@ -56,13 +57,13 @@ class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementF
|
|||||||
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
|
||||||
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
|
||||||
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
|
||||||
val proposedTx = stx.tx
|
val proposedTx = stx.coreTransaction as ContractUpgradeWireTransaction
|
||||||
val expectedTx = ContractUpgradeUtils.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction(serviceHub)
|
val expectedTx = ContractUpgradeUtils.assembleUpgradeTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt, serviceHub)
|
||||||
requireThat {
|
requireThat {
|
||||||
"The instigator is one of the participants" using (initiatingSession.counterparty in oldStateAndRef.state.data.participants)
|
"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 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)
|
||||||
}
|
}
|
||||||
proposedTx.toLedgerTransaction(serviceHub).verify()
|
proposedTx.resolve(serviceHub, stx.sigs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,6 @@ interface ServiceHubInternal : ServiceHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (statesToRecord != StatesToRecord.NONE) {
|
if (statesToRecord != StatesToRecord.NONE) {
|
||||||
val toNotify = recordedTransactions.map { if (it.isNotaryChangeTransaction()) it.notaryChangeTx else it.tx }
|
|
||||||
// When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
|
// When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
|
||||||
// that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
|
// that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
|
||||||
// is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
|
// is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
|
||||||
@ -116,7 +115,7 @@ interface ServiceHubInternal : ServiceHub {
|
|||||||
//
|
//
|
||||||
// Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
|
// Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
|
||||||
// to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
|
// to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
|
||||||
vaultService.notifyAll(statesToRecord, toNotify)
|
vaultService.notifyAll(statesToRecord, txs.map { it.coreTransaction })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import net.corda.core.internal.VisibleForTesting
|
|||||||
import net.corda.core.internal.concurrent.flatMap
|
import net.corda.core.internal.concurrent.flatMap
|
||||||
import net.corda.core.internal.join
|
import net.corda.core.internal.join
|
||||||
import net.corda.core.internal.until
|
import net.corda.core.internal.until
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.schemas.PersistentStateRef
|
import net.corda.core.schemas.PersistentStateRef
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
@ -58,7 +58,7 @@ import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture
|
|||||||
class NodeSchedulerService(private val clock: CordaClock,
|
class NodeSchedulerService(private val clock: CordaClock,
|
||||||
private val database: CordaPersistence,
|
private val database: CordaPersistence,
|
||||||
private val flowStarter: FlowStarter,
|
private val flowStarter: FlowStarter,
|
||||||
private val stateLoader: StateLoader,
|
private val servicesForResolution: ServicesForResolution,
|
||||||
private val unfinishedSchedules: ReusableLatch = ReusableLatch(),
|
private val unfinishedSchedules: ReusableLatch = ReusableLatch(),
|
||||||
private val serverThread: Executor,
|
private val serverThread: Executor,
|
||||||
private val flowLogicRefFactory: FlowLogicRefFactory,
|
private val flowLogicRefFactory: FlowLogicRefFactory,
|
||||||
@ -311,7 +311,7 @@ class NodeSchedulerService(private val clock: CordaClock,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getScheduledActivity(scheduledState: ScheduledStateRef): ScheduledActivity? {
|
private fun getScheduledActivity(scheduledState: ScheduledStateRef): ScheduledActivity? {
|
||||||
val txState = stateLoader.loadState(scheduledState.ref)
|
val txState = servicesForResolution.loadState(scheduledState.ref)
|
||||||
val state = txState.data as SchedulableState
|
val state = txState.data as SchedulableState
|
||||||
return try {
|
return try {
|
||||||
// This can throw as running contract code.
|
// This can throw as running contract code.
|
||||||
|
@ -10,6 +10,7 @@ import net.corda.core.flows.NotarisationRequest
|
|||||||
import net.corda.core.internal.validateRequest
|
import net.corda.core.internal.validateRequest
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.CoreTransaction
|
||||||
|
import net.corda.core.transactions.ContractUpgradeFilteredTransaction
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -44,6 +45,7 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAut
|
|||||||
val notary = tx.notary
|
val notary = tx.notary
|
||||||
TransactionParts(tx.id, tx.inputs, tx.timeWindow, notary)
|
TransactionParts(tx.id, tx.inputs, tx.timeWindow, notary)
|
||||||
}
|
}
|
||||||
|
is ContractUpgradeFilteredTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
|
||||||
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
|
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
|
||||||
else -> {
|
else -> {
|
||||||
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +
|
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +
|
||||||
|
@ -4,13 +4,12 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.contracts.TransactionVerificationException
|
import net.corda.core.contracts.TransactionVerificationException
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.NotarisationPayload
|
|
||||||
import net.corda.core.flows.NotarisationRequest
|
|
||||||
import net.corda.core.internal.ResolveTransactionsFlow
|
import net.corda.core.internal.ResolveTransactionsFlow
|
||||||
import net.corda.core.internal.validateRequest
|
import net.corda.core.internal.validateRequest
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionWithSignatures
|
import net.corda.core.transactions.TransactionWithSignatures
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
|
|
||||||
@ -31,12 +30,9 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthor
|
|||||||
val stx = receiveTransaction()
|
val stx = receiveTransaction()
|
||||||
val notary = stx.notary
|
val notary = stx.notary
|
||||||
checkNotary(notary)
|
checkNotary(notary)
|
||||||
val timeWindow: TimeWindow? = if (stx.isNotaryChangeTransaction())
|
|
||||||
null
|
|
||||||
else
|
|
||||||
stx.tx.timeWindow
|
|
||||||
resolveAndContractVerify(stx)
|
resolveAndContractVerify(stx)
|
||||||
verifySignatures(stx)
|
verifySignatures(stx)
|
||||||
|
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
|
||||||
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw when (e) {
|
throw when (e) {
|
||||||
|
@ -6,15 +6,13 @@ import net.corda.core.contracts.*
|
|||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
import net.corda.core.node.services.vault.*
|
import net.corda.core.node.services.vault.*
|
||||||
import net.corda.core.schemas.PersistentStateRef
|
import net.corda.core.schemas.PersistentStateRef
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.*
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
|
||||||
import net.corda.core.transactions.WireTransaction
|
|
||||||
import net.corda.core.utilities.*
|
import net.corda.core.utilities.*
|
||||||
import net.corda.node.services.api.VaultServiceInternal
|
import net.corda.node.services.api.VaultServiceInternal
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
@ -49,7 +47,7 @@ private fun CriteriaBuilder.executeUpdate(session: Session, configure: Root<*>.(
|
|||||||
class NodeVaultService(
|
class NodeVaultService(
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val keyManagementService: KeyManagementService,
|
private val keyManagementService: KeyManagementService,
|
||||||
private val stateLoader: StateLoader,
|
private val servicesForResolution: ServicesForResolution,
|
||||||
hibernateConfig: HibernateConfiguration
|
hibernateConfig: HibernateConfiguration
|
||||||
) : SingletonSerializeAsToken(), VaultServiceInternal {
|
) : SingletonSerializeAsToken(), VaultServiceInternal {
|
||||||
private companion object {
|
private companion object {
|
||||||
@ -108,41 +106,29 @@ class NodeVaultService(
|
|||||||
override val updates: Observable<Vault.Update<ContractState>>
|
override val updates: Observable<Vault.Update<ContractState>>
|
||||||
get() = mutex.locked { _updatesInDbTx }
|
get() = mutex.locked { _updatesInDbTx }
|
||||||
|
|
||||||
|
/** Groups adjacent transactions into batches to generate separate net updates per transaction type. */
|
||||||
override fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable<CoreTransaction>) {
|
override fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable<CoreTransaction>) {
|
||||||
if (statesToRecord == StatesToRecord.NONE)
|
if (statesToRecord == StatesToRecord.NONE || !txns.any()) return
|
||||||
return
|
val batch = mutableListOf<CoreTransaction>()
|
||||||
|
|
||||||
// It'd be easier to just group by type, but then we'd lose ordering.
|
fun flushBatch() {
|
||||||
val regularTxns = mutableListOf<WireTransaction>()
|
val updates = makeUpdates(batch, statesToRecord)
|
||||||
val notaryChangeTxns = mutableListOf<NotaryChangeWireTransaction>()
|
processAndNotify(updates)
|
||||||
|
batch.clear()
|
||||||
for (tx in txns) {
|
|
||||||
when (tx) {
|
|
||||||
is WireTransaction -> {
|
|
||||||
regularTxns.add(tx)
|
|
||||||
if (notaryChangeTxns.isNotEmpty()) {
|
|
||||||
notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord)
|
|
||||||
notaryChangeTxns.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is NotaryChangeWireTransaction -> {
|
|
||||||
notaryChangeTxns.add(tx)
|
|
||||||
if (regularTxns.isNotEmpty()) {
|
|
||||||
notifyRegular(regularTxns.toList(), statesToRecord)
|
|
||||||
regularTxns.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (regularTxns.isNotEmpty()) notifyRegular(regularTxns.toList(), statesToRecord)
|
for (tx in txns) {
|
||||||
if (notaryChangeTxns.isNotEmpty()) notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord)
|
if (batch.isNotEmpty() && tx.javaClass != batch.last().javaClass) {
|
||||||
|
flushBatch()
|
||||||
|
}
|
||||||
|
batch.add(tx)
|
||||||
|
}
|
||||||
|
flushBatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyRegular(txns: Iterable<WireTransaction>, statesToRecord: StatesToRecord) {
|
private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord): List<Vault.Update<ContractState>> {
|
||||||
fun makeUpdate(tx: WireTransaction): Vault.Update<ContractState> {
|
fun makeUpdate(tx: WireTransaction): Vault.Update<ContractState>? {
|
||||||
val myKeys = keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } })
|
val myKeys = keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } })
|
||||||
|
|
||||||
val ourNewStates = when (statesToRecord) {
|
val ourNewStates = when (statesToRecord) {
|
||||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
||||||
StatesToRecord.ONLY_RELEVANT -> tx.outputs.filter { isRelevant(it.data, myKeys.toSet()) }
|
StatesToRecord.ONLY_RELEVANT -> tx.outputs.filter { isRelevant(it.data, myKeys.toSet()) }
|
||||||
@ -155,45 +141,48 @@ class NodeVaultService(
|
|||||||
// Is transaction irrelevant?
|
// Is transaction irrelevant?
|
||||||
if (consumedStates.isEmpty() && ourNewStates.isEmpty()) {
|
if (consumedStates.isEmpty() && ourNewStates.isEmpty()) {
|
||||||
log.trace { "tx ${tx.id} was irrelevant to this vault, ignoring" }
|
log.trace { "tx ${tx.id} was irrelevant to this vault, ignoring" }
|
||||||
return Vault.NoUpdate
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return Vault.Update(consumedStates.toSet(), ourNewStates.toSet())
|
return Vault.Update(consumedStates.toSet(), ourNewStates.toSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
val netDelta = txns.fold(Vault.NoUpdate) { netDelta, txn -> netDelta + makeUpdate(txn) }
|
fun resolveAndMakeUpdate(tx: CoreTransaction): Vault.Update<ContractState>? {
|
||||||
processAndNotify(netDelta)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun notifyNotaryChange(txns: Iterable<NotaryChangeWireTransaction>, statesToRecord: StatesToRecord) {
|
|
||||||
fun makeUpdate(tx: NotaryChangeWireTransaction): Vault.Update<ContractState> {
|
|
||||||
// We need to resolve the full transaction here because outputs are calculated from inputs
|
// We need to resolve the full transaction here because outputs are calculated from inputs
|
||||||
// We also can't do filtering beforehand, since output encumbrance pointers get recalculated based on
|
// We also can't do filtering beforehand, since for notary change transactions output encumbrance pointers
|
||||||
// input positions
|
// get recalculated based on input positions.
|
||||||
val ltx = tx.resolve(stateLoader, emptyList())
|
val ltx: FullTransaction = when (tx) {
|
||||||
|
is NotaryChangeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
|
||||||
|
is ContractUpgradeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
|
||||||
|
else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}")
|
||||||
|
}
|
||||||
val myKeys = keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } })
|
val myKeys = keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } })
|
||||||
val (consumedStateAndRefs, producedStates) = ltx.inputs.
|
val (consumedStateAndRefs, producedStates) = ltx.inputs.
|
||||||
zip(ltx.outputs).
|
zip(ltx.outputs).
|
||||||
filter { (_, output) ->
|
filter { (_, output) ->
|
||||||
if (statesToRecord == StatesToRecord.ONLY_RELEVANT)
|
if (statesToRecord == StatesToRecord.ONLY_RELEVANT) isRelevant(output.data, myKeys.toSet())
|
||||||
isRelevant(output.data, myKeys.toSet())
|
else true
|
||||||
else
|
|
||||||
true
|
|
||||||
}.
|
}.
|
||||||
unzip()
|
unzip()
|
||||||
|
|
||||||
val producedStateAndRefs = producedStates.map { ltx.outRef<ContractState>(it.data) }
|
val producedStateAndRefs = producedStates.map { ltx.outRef<ContractState>(it.data) }
|
||||||
|
|
||||||
if (consumedStateAndRefs.isEmpty() && producedStateAndRefs.isEmpty()) {
|
if (consumedStateAndRefs.isEmpty() && producedStateAndRefs.isEmpty()) {
|
||||||
log.trace { "tx ${tx.id} was irrelevant to this vault, ignoring" }
|
log.trace { "tx ${tx.id} was irrelevant to this vault, ignoring" }
|
||||||
return Vault.NoNotaryUpdate
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return Vault.Update(consumedStateAndRefs.toHashSet(), producedStateAndRefs.toHashSet(), null, Vault.UpdateType.NOTARY_CHANGE)
|
val updateType = if (tx is ContractUpgradeWireTransaction) {
|
||||||
|
Vault.UpdateType.CONTRACT_UPGRADE
|
||||||
|
} else {
|
||||||
|
Vault.UpdateType.NOTARY_CHANGE
|
||||||
|
}
|
||||||
|
return Vault.Update(consumedStateAndRefs.toSet(), producedStateAndRefs.toSet(), null, updateType)
|
||||||
}
|
}
|
||||||
|
|
||||||
val netDelta = txns.fold(Vault.NoNotaryUpdate) { netDelta, txn -> netDelta + makeUpdate(txn) }
|
|
||||||
processAndNotify(netDelta)
|
return batch.mapNotNull {
|
||||||
|
if (it is WireTransaction) makeUpdate(it) else resolveAndMakeUpdate(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadStates(refs: Collection<StateRef>): Collection<StateAndRef<ContractState>> {
|
private fun loadStates(refs: Collection<StateRef>): Collection<StateAndRef<ContractState>> {
|
||||||
@ -202,13 +191,15 @@ class NodeVaultService(
|
|||||||
else emptySet()
|
else emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processAndNotify(update: Vault.Update<ContractState>) {
|
private fun processAndNotify(updates: List<Vault.Update<ContractState>>) {
|
||||||
if (!update.isEmpty()) {
|
if (updates.isEmpty()) return
|
||||||
recordUpdate(update)
|
val netUpdate = updates.reduce { update1, update2 -> update1 + update2 }
|
||||||
|
if (!netUpdate.isEmpty()) {
|
||||||
|
recordUpdate(netUpdate)
|
||||||
mutex.locked {
|
mutex.locked {
|
||||||
// flowId required by SoftLockManager to perform auto-registration of soft locks for new states
|
// flowId required by SoftLockManager to perform auto-registration of soft locks for new states
|
||||||
val uuid = (Strand.currentStrand() as? FlowStateMachineImpl<*>)?.id?.uuid
|
val uuid = (Strand.currentStrand() as? FlowStateMachineImpl<*>)?.id?.uuid
|
||||||
val vaultUpdate = if (uuid != null) update.copy(flowId = uuid) else update
|
val vaultUpdate = if (uuid != null) netUpdate.copy(flowId = uuid) else netUpdate
|
||||||
updatesPublisher.onNext(vaultUpdate)
|
updatesPublisher.onNext(vaultUpdate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,7 +448,7 @@ class NodeVaultService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stateRefs.isNotEmpty())
|
if (stateRefs.isNotEmpty())
|
||||||
statesAndRefs.addAll(stateLoader.loadStates(stateRefs) as Collection<StateAndRef<T>>)
|
statesAndRefs.addAll(servicesForResolution.loadStates(stateRefs) as Collection<StateAndRef<T>>)
|
||||||
|
|
||||||
return Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults)
|
return Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults)
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
|
@ -9,7 +9,7 @@ import net.corda.core.flows.FlowLogicRefFactory
|
|||||||
import net.corda.core.internal.FlowStateMachine
|
import net.corda.core.internal.FlowStateMachine
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.node.services.api.FlowStarter
|
import net.corda.node.services.api.FlowStarter
|
||||||
import net.corda.node.services.api.NodePropertiesStore
|
import net.corda.node.services.api.NodePropertiesStore
|
||||||
@ -48,7 +48,7 @@ class NodeSchedulerServiceTest {
|
|||||||
doReturn(flowsDraingMode).whenever(it).flowsDrainingMode
|
doReturn(flowsDraingMode).whenever(it).flowsDrainingMode
|
||||||
}
|
}
|
||||||
private val transactionStates = mutableMapOf<StateRef, TransactionState<*>>()
|
private val transactionStates = mutableMapOf<StateRef, TransactionState<*>>()
|
||||||
private val stateLoader = rigorousMock<StateLoader>().also {
|
private val servicesForResolution = rigorousMock<ServicesForResolution>().also {
|
||||||
doLookup(transactionStates).whenever(it).loadState(any())
|
doLookup(transactionStates).whenever(it).loadState(any())
|
||||||
}
|
}
|
||||||
private val flows = mutableMapOf<FlowLogicRef, FlowLogic<*>>()
|
private val flows = mutableMapOf<FlowLogicRef, FlowLogic<*>>()
|
||||||
@ -63,7 +63,7 @@ class NodeSchedulerServiceTest {
|
|||||||
testClock,
|
testClock,
|
||||||
database,
|
database,
|
||||||
flowStarter,
|
flowStarter,
|
||||||
stateLoader,
|
servicesForResolution,
|
||||||
serverThread = MoreExecutors.directExecutor(),
|
serverThread = MoreExecutors.directExecutor(),
|
||||||
flowLogicRefFactory = flowLogicRefFactory,
|
flowLogicRefFactory = flowLogicRefFactory,
|
||||||
nodeProperties = nodeProperties,
|
nodeProperties = nodeProperties,
|
||||||
|
@ -118,7 +118,7 @@ class HibernateConfigurationTest {
|
|||||||
services = object : MockServices(cordappPackages, BOB_NAME, rigorousMock<IdentityServiceInternal>().also {
|
services = object : MockServices(cordappPackages, BOB_NAME, rigorousMock<IdentityServiceInternal>().also {
|
||||||
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME })
|
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME })
|
||||||
}, generateKeyPair(), dummyNotary.keyPair) {
|
}, generateKeyPair(), dummyNotary.keyPair) {
|
||||||
override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, validatedTransactions, hibernateConfig)
|
override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig)
|
||||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||||
for (stx in txs) {
|
for (stx in txs) {
|
||||||
validatedTransactions.addTransaction(stx)
|
validatedTransactions.addTransaction(stx)
|
||||||
|
@ -11,7 +11,7 @@ import net.corda.core.identity.AbstractParty
|
|||||||
import net.corda.core.internal.FlowStateMachine
|
import net.corda.core.internal.FlowStateMachine
|
||||||
import net.corda.core.internal.packageName
|
import net.corda.core.internal.packageName
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import net.corda.core.node.services.queryBy
|
import net.corda.core.node.services.queryBy
|
||||||
import net.corda.core.node.services.vault.QueryCriteria.SoftLockingCondition
|
import net.corda.core.node.services.vault.QueryCriteria.SoftLockingCondition
|
||||||
@ -83,8 +83,8 @@ class VaultSoftLockManagerTest {
|
|||||||
}
|
}
|
||||||
private val mockNet = InternalMockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { args ->
|
private val mockNet = InternalMockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { args ->
|
||||||
object : InternalMockNetwork.MockNode(args) {
|
object : InternalMockNetwork.MockNode(args) {
|
||||||
override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
||||||
val realVault = super.makeVaultService(keyManagementService, stateLoader, hibernateConfig)
|
val realVault = super.makeVaultService(keyManagementService, services, hibernateConfig)
|
||||||
return object : VaultServiceInternal by realVault {
|
return object : VaultServiceInternal by realVault {
|
||||||
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) {
|
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) {
|
||||||
mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests.
|
mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests.
|
||||||
|
@ -11,8 +11,10 @@ import net.corda.core.internal.validateRequest
|
|||||||
import net.corda.core.node.AppServiceHub
|
import net.corda.core.node.AppServiceHub
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
|
import net.corda.core.transactions.CoreTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionWithSignatures
|
import net.corda.core.transactions.TransactionWithSignatures
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -49,12 +51,9 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
|
|||||||
val stx = receiveTransaction()
|
val stx = receiveTransaction()
|
||||||
val notary = stx.notary
|
val notary = stx.notary
|
||||||
checkNotary(notary)
|
checkNotary(notary)
|
||||||
val timeWindow: TimeWindow? = if (stx.isNotaryChangeTransaction())
|
|
||||||
null
|
|
||||||
else
|
|
||||||
stx.tx.timeWindow
|
|
||||||
resolveAndContractVerify(stx)
|
|
||||||
verifySignatures(stx)
|
verifySignatures(stx)
|
||||||
|
resolveAndContractVerify(stx)
|
||||||
|
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
|
||||||
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw when (e) {
|
throw when (e) {
|
||||||
|
@ -2,6 +2,9 @@ package net.corda.testing.node
|
|||||||
|
|
||||||
import com.google.common.collect.MutableClassToInstanceMap
|
import com.google.common.collect.MutableClassToInstanceMap
|
||||||
import net.corda.core.contracts.ContractClassName
|
import net.corda.core.contracts.ContractClassName
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.contracts.StateAndRef
|
||||||
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
@ -17,6 +20,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
|
import net.corda.node.internal.ServicesForResolutionImpl
|
||||||
import net.corda.node.internal.configureDatabase
|
import net.corda.node.internal.configureDatabase
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import net.corda.node.services.api.SchemaService
|
import net.corda.node.services.api.SchemaService
|
||||||
@ -65,8 +69,7 @@ open class MockServices private constructor(
|
|||||||
final override val networkParameters: NetworkParameters,
|
final override val networkParameters: NetworkParameters,
|
||||||
private val initialIdentity: TestIdentity,
|
private val initialIdentity: TestIdentity,
|
||||||
private val moreKeys: Array<out KeyPair>
|
private val moreKeys: Array<out KeyPair>
|
||||||
) : ServiceHub, StateLoader by validatedTransactions {
|
) : ServiceHub {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")
|
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")
|
||||||
@ -113,7 +116,7 @@ open class MockServices private constructor(
|
|||||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||||
super.recordTransactions(statesToRecord, txs)
|
super.recordTransactions(statesToRecord, txs)
|
||||||
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
||||||
vaultService.notifyAll(statesToRecord, txs.map { it.tx })
|
vaultService.notifyAll(statesToRecord, txs.map { it.coreTransaction })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun jdbcSession(): Connection = database.createSession()
|
override fun jdbcSession(): Connection = database.createSession()
|
||||||
@ -172,7 +175,7 @@ open class MockServices private constructor(
|
|||||||
/**
|
/**
|
||||||
* Create a mock [ServiceHub] that can't load CorDapp code, and which uses a default service identity.
|
* Create a mock [ServiceHub] that can't load CorDapp code, and which uses a default service identity.
|
||||||
*/
|
*/
|
||||||
constructor(cordappPackages: List<String>): this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
constructor(cordappPackages: List<String>) : this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service
|
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service
|
||||||
@ -207,7 +210,8 @@ open class MockServices private constructor(
|
|||||||
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses a default service
|
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses a default service
|
||||||
* identity.
|
* identity.
|
||||||
*/
|
*/
|
||||||
constructor(): this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
constructor() : this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
||||||
|
|
||||||
|
|
||||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||||
txs.forEach {
|
txs.forEach {
|
||||||
@ -229,8 +233,10 @@ open class MockServices private constructor(
|
|||||||
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments, networkParameters.whitelistedContractImplementations)
|
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments, networkParameters.whitelistedContractImplementations)
|
||||||
override val cordappProvider: CordappProvider get() = mockCordappProvider
|
override val cordappProvider: CordappProvider get() = mockCordappProvider
|
||||||
|
|
||||||
|
protected val servicesForResolution: ServicesForResolution get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, validatedTransactions)
|
||||||
|
|
||||||
internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal {
|
internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal {
|
||||||
val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, validatedTransactions, hibernateConfig)
|
val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig)
|
||||||
HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService)
|
HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService)
|
||||||
return vaultService
|
return vaultService
|
||||||
}
|
}
|
||||||
@ -249,6 +255,9 @@ open class MockServices private constructor(
|
|||||||
fun addMockCordapp(contractClassName: ContractClassName) {
|
fun addMockCordapp(contractClassName: ContractClassName) {
|
||||||
mockCordappProvider.addMockCordapp(contractClassName, attachments)
|
mockCordappProvider.addMockCordapp(contractClassName, attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun loadState(stateRef: StateRef) = servicesForResolution.loadState(stateRef)
|
||||||
|
override fun loadStates(stateRefs: Set<StateRef>) = servicesForResolution.loadStates(stateRefs)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockKeyManagementService(val identityService: IdentityService,
|
class MockKeyManagementService(val identityService: IdentityService,
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
package net.corda.testing.contracts
|
package net.corda.testing.contracts
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
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.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import net.corda.core.transactions.WireTransaction
|
|
||||||
|
|
||||||
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
||||||
|
|
||||||
@ -14,12 +11,13 @@ import net.corda.core.transactions.WireTransaction
|
|||||||
* Dummy contract state for testing of the upgrade process.
|
* Dummy contract state for testing of the upgrade process.
|
||||||
*/
|
*/
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
|
class DummyContractV2 : UpgradedContractWithLegacyConstraint<DummyContract.State, DummyContractV2.State> {
|
||||||
companion object {
|
companion object {
|
||||||
const val PROGRAM_ID: ContractClassName = "net.corda.testing.contracts.DummyContractV2"
|
const val PROGRAM_ID: ContractClassName = "net.corda.testing.contracts.DummyContractV2"
|
||||||
}
|
}
|
||||||
|
|
||||||
override val legacyContract: String = DummyContract::class.java.name
|
override val legacyContract: String = DummyContract::class.java.name
|
||||||
|
override val legacyContractConstraint: AttachmentConstraint = AlwaysAcceptAttachmentConstraint
|
||||||
|
|
||||||
data class State(val magicNumber: Int = 0, val owners: List<AbstractParty>) : ContractState {
|
data class State(val magicNumber: Int = 0, val owners: List<AbstractParty>) : ContractState {
|
||||||
override val participants: List<AbstractParty> = owners
|
override val participants: List<AbstractParty> = owners
|
||||||
@ -38,25 +36,4 @@ class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.St
|
|||||||
// Other verifications.
|
// Other verifications.
|
||||||
}
|
}
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
/**
|
|
||||||
* Generate an upgrade transaction from [DummyContract].
|
|
||||||
*
|
|
||||||
* Note: This is a convenience helper method used for testing only.
|
|
||||||
*
|
|
||||||
* @param services Services required to resolve the wire transaction
|
|
||||||
* @return a pair of wire transaction, and a set of those who should sign the transaction for it to be valid.
|
|
||||||
*/
|
|
||||||
fun generateUpgradeFromV1(services: ServicesForResolution, vararg states: StateAndRef<DummyContract.State>): Pair<WireTransaction, Set<AbstractParty>> {
|
|
||||||
val notary = states.map { it.state.notary }.single()
|
|
||||||
require(states.isNotEmpty())
|
|
||||||
|
|
||||||
val signees: Set<AbstractParty> = states.flatMap { it.state.data.participants }.distinct().toSet()
|
|
||||||
return Pair(TransactionBuilder(notary).apply {
|
|
||||||
states.forEach {
|
|
||||||
addInputState(it)
|
|
||||||
addOutputState(upgrade(it.state.data), DummyContractV2.PROGRAM_ID, it.state.constraint)
|
|
||||||
addCommand(UpgradeCommand(DummyContractV2.PROGRAM_ID), signees.map { it.owningKey }.toList())
|
|
||||||
}
|
|
||||||
}.toWireTransaction(services), signees)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user