mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
CORDA-696 - Ensure deterministic transaction id calculation for contra… (#2676)
The problem with the previous implementation is that the transaction would be deserialized with the schema specified in the serialized form, but the calculation of the id would involve re-serializing properties using a local serialization context which might produce a different result.
This commit is contained in:
parent
af60848da7
commit
a3bf4577f3
@ -3001,17 +3001,15 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
|
||||
@org.jetbrains.annotations.NotNull public final String getReason()
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeFilteredTransaction extends net.corda.core.transactions.CoreTransaction
|
||||
public <init>(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash)
|
||||
@org.jetbrains.annotations.NotNull public final List component1()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component3()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash)
|
||||
public <init>(Map, Map)
|
||||
@org.jetbrains.annotations.NotNull public final Map component1()
|
||||
@org.jetbrains.annotations.NotNull public final Map component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(Map, Map)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
|
||||
@org.jetbrains.annotations.NotNull public List getInputs()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
|
||||
@org.jetbrains.annotations.NotNull public List getOutputs()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getRest()
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
##
|
||||
@ -3038,8 +3036,8 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
|
||||
@org.jetbrains.annotations.NotNull public Set getRequiredSigningKeys()
|
||||
@org.jetbrains.annotations.NotNull public List getSigs()
|
||||
@org.jetbrains.annotations.NotNull public final String getUpgradeContractClassName()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getUpgradedContractAttachment()
|
||||
@org.jetbrains.annotations.NotNull public final String getUpgradedContractClassName()
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
public void verifyRequiredSignatures()
|
||||
@ -3047,15 +3045,11 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
|
||||
public void verifySignaturesExcept(java.security.PublicKey...)
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeWireTransaction extends net.corda.core.transactions.CoreTransaction
|
||||
public <init>(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, String, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt)
|
||||
public <init>(List, net.corda.core.contracts.PrivacySalt)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction buildFilteredTransaction()
|
||||
@org.jetbrains.annotations.NotNull public final List component1()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component3()
|
||||
@org.jetbrains.annotations.NotNull public final String component4()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component5()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component6()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeWireTransaction copy(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, String, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeWireTransaction copy(List, net.corda.core.contracts.PrivacySalt)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
|
||||
@org.jetbrains.annotations.NotNull public List getInputs()
|
||||
@ -3063,8 +3057,8 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
|
||||
@org.jetbrains.annotations.NotNull public List getOutputs()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
|
||||
@org.jetbrains.annotations.NotNull public final String getUpgradeContractClassName()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getUpgradedContractAttachmentId()
|
||||
@org.jetbrains.annotations.NotNull public final String getUpgradedContractClassName()
|
||||
public int hashCode()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, List)
|
||||
public String toString()
|
||||
@ -3199,11 +3193,9 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
|
||||
public void verifySignaturesExcept(java.security.PublicKey...)
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction
|
||||
public <init>(List, net.corda.core.identity.Party, net.corda.core.identity.Party)
|
||||
public <init>(List)
|
||||
@org.jetbrains.annotations.NotNull public final List component1()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component3()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List, net.corda.core.identity.Party, net.corda.core.identity.Party)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
|
||||
@org.jetbrains.annotations.NotNull public List getInputs()
|
||||
|
@ -222,7 +222,11 @@ fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentG
|
||||
/** Return the SHA256(SHA256(nonce || serializedComponent)). */
|
||||
fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = SecureHash.sha256Twice(nonce.bytes + opaqueBytes.bytes)
|
||||
|
||||
/** Serialise the object and return the hash of the serialized bytes. */
|
||||
/**
|
||||
* Serialise the object and return the hash of the serialized bytes. Note that the resulting hash may not be deterministic
|
||||
* across platform versions: serialization can produce different values if any of the types being serialized have changed,
|
||||
* or if the version of serialization specified by the context changes.
|
||||
*/
|
||||
fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes.sha256()
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignableData
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.internal.NotaryChangeTransactionBuilder
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
|
||||
@ -30,11 +30,11 @@ class NotaryChangeFlow<out T : ContractState>(
|
||||
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||
val inputs = resolveEncumbrances(originalState)
|
||||
|
||||
val tx = NotaryChangeWireTransaction(
|
||||
val tx = NotaryChangeTransactionBuilder(
|
||||
inputs.map { it.ref },
|
||||
originalState.state.notary,
|
||||
modification
|
||||
)
|
||||
).build()
|
||||
|
||||
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()
|
||||
// TODO: We need a much faster way of finding our key in the transaction
|
||||
|
@ -21,18 +21,18 @@ object ContractUpgradeUtils {
|
||||
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
|
||||
|
||||
val inputs = listOf(stateAndRef.ref)
|
||||
return ContractUpgradeWireTransaction(
|
||||
return ContractUpgradeTransactionBuilder(
|
||||
inputs,
|
||||
stateAndRef.state.notary,
|
||||
legacyContractAttachmentId,
|
||||
upgradedContractClass.name,
|
||||
upgradedContractAttachmentId,
|
||||
privacySalt
|
||||
)
|
||||
).build()
|
||||
}
|
||||
|
||||
private fun getContractAttachmentId(name: ContractClassName, services: ServicesForResolution): AttachmentId {
|
||||
return services.cordappProvider.getContractAttachmentID(name)
|
||||
?: throw IllegalStateException("Attachment not found for contract: $name")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/** Constructs a [NotaryChangeWireTransaction]. */
|
||||
class NotaryChangeTransactionBuilder(val inputs: List<StateRef>,
|
||||
val notary: Party,
|
||||
val newNotary: Party) {
|
||||
fun build(): NotaryChangeWireTransaction {
|
||||
val components = listOf(inputs, notary, newNotary).map { it.serialize() }
|
||||
return NotaryChangeWireTransaction(components)
|
||||
}
|
||||
}
|
||||
|
||||
/** Constructs a [ContractUpgradeWireTransaction]. */
|
||||
class ContractUpgradeTransactionBuilder(
|
||||
val inputs: List<StateRef>,
|
||||
val notary: Party,
|
||||
val legacyContractAttachmentId: SecureHash,
|
||||
val upgradedContractClassName: ContractClassName,
|
||||
val upgradedContractAttachmentId: SecureHash,
|
||||
val privacySalt: PrivacySalt = PrivacySalt()) {
|
||||
fun build(): ContractUpgradeWireTransaction {
|
||||
val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId).map { it.serialize() }
|
||||
return ContractUpgradeWireTransaction(components, privacySalt)
|
||||
}
|
||||
}
|
||||
|
||||
/** Concatenates the hash components into a single [ByteArray] and returns its hash. */
|
||||
fun combinedHash(components: Iterable<SecureHash>): SecureHash {
|
||||
val stream = ByteArrayOutputStream()
|
||||
components.forEach {
|
||||
stream.write(it.bytes)
|
||||
}
|
||||
return stream.toByteArray().sha256()
|
||||
}
|
@ -3,12 +3,18 @@ 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.crypto.componentHash
|
||||
import net.corda.core.crypto.computeNonce
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.AttachmentWithContext
|
||||
import net.corda.core.internal.combinedHash
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.transactions.ContractUpgradeFilteredTransaction.FilteredComponent
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import java.security.PublicKey
|
||||
|
||||
@ -18,13 +24,20 @@ import java.security.PublicKey
|
||||
/** 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,
|
||||
/**
|
||||
* Contains all of the transaction components in serialized form.
|
||||
* This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties
|
||||
* may result in a different byte sequence depending on the serialization context.
|
||||
*/
|
||||
val serializedComponents: List<OpaqueBytes>,
|
||||
/** Required for hiding components in [ContractUpgradeFilteredTransaction]. */
|
||||
val privacySalt: PrivacySalt = PrivacySalt()
|
||||
) : CoreTransaction() {
|
||||
override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
|
||||
override val notary: Party by lazy { serializedComponents[NOTARY.ordinal].deserialize<Party>() }
|
||||
val legacyContractAttachmentId: SecureHash by lazy { serializedComponents[LEGACY_ATTACHMENT.ordinal].deserialize<SecureHash>() }
|
||||
val upgradedContractClassName: ContractClassName by lazy { serializedComponents[UPGRADED_CONTRACT.ordinal].deserialize<ContractClassName>() }
|
||||
val upgradedContractAttachmentId: SecureHash by lazy { serializedComponents[UPGRADED_ATTACHMENT.ordinal].deserialize<SecureHash>() }
|
||||
|
||||
init {
|
||||
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
|
||||
@ -39,11 +52,17 @@ data class ContractUpgradeWireTransaction(
|
||||
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 {
|
||||
val componentHashes =serializedComponents.mapIndexed { index, component ->
|
||||
componentHash(nonces[index], component)
|
||||
}
|
||||
combinedHash(componentHashes)
|
||||
}
|
||||
|
||||
override val id: SecureHash by lazy { serializedHash(inputs + notary).hashConcat(hiddenComponentHash) }
|
||||
/** Required for filtering transaction components. */
|
||||
private val nonces = (0 until serializedComponents.size).map {
|
||||
computeNonce(privacySalt, it, 0)
|
||||
}
|
||||
|
||||
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */
|
||||
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
|
||||
@ -56,7 +75,7 @@ data class ContractUpgradeWireTransaction(
|
||||
resolvedInputs,
|
||||
notary,
|
||||
legacyContractAttachment,
|
||||
upgradeContractClassName,
|
||||
upgradedContractClassName,
|
||||
upgradedContractAttachment,
|
||||
id,
|
||||
privacySalt,
|
||||
@ -65,8 +84,23 @@ data class ContractUpgradeWireTransaction(
|
||||
)
|
||||
}
|
||||
|
||||
/** Constructs a filtered transaction: the inputs and the notary party are always visible, while the rest are hidden. */
|
||||
fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
|
||||
return ContractUpgradeFilteredTransaction(inputs, notary, hiddenComponentHash)
|
||||
val totalComponents = (0 until serializedComponents.size).toSet()
|
||||
val visibleComponents = mapOf(
|
||||
INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]),
|
||||
NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal])
|
||||
)
|
||||
val hiddenComponents = (totalComponents - visibleComponents.keys).map { index ->
|
||||
val hash = componentHash(nonces[index], serializedComponents[index])
|
||||
index to hash
|
||||
}.toMap()
|
||||
|
||||
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents)
|
||||
}
|
||||
|
||||
enum class Component {
|
||||
INPUTS, NOTARY, LEGACY_ATTACHMENT, UPGRADED_CONTRACT, UPGRADED_ATTACHMENT
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,19 +108,43 @@ data class ContractUpgradeWireTransaction(
|
||||
* 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
|
||||
/** Transaction components that are exposed. */
|
||||
val visibleComponents: Map<Int, FilteredComponent>,
|
||||
/**
|
||||
* Hashes of the transaction components that are not revealed in this transaction.
|
||||
* Required for computing the transaction id.
|
||||
*/
|
||||
val hiddenComponents: Map<Int, SecureHash>
|
||||
) : CoreTransaction() {
|
||||
override val id: SecureHash get() = serializedHash(inputs + notary).hashConcat(rest)
|
||||
override val inputs: List<StateRef> by lazy {
|
||||
visibleComponents[INPUTS.ordinal]?.component?.deserialize<List<StateRef>>()
|
||||
?: throw IllegalArgumentException("Inputs not specified")
|
||||
}
|
||||
override val notary: Party by lazy {
|
||||
visibleComponents[NOTARY.ordinal]?.component?.deserialize<Party>()
|
||||
?: throw IllegalArgumentException("Notary not specified")
|
||||
}
|
||||
override val id: SecureHash by lazy {
|
||||
val totalComponents = visibleComponents.size + hiddenComponents.size
|
||||
val hashList = (0 until totalComponents).map { i ->
|
||||
when {
|
||||
visibleComponents.containsKey(i) -> {
|
||||
componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component)
|
||||
}
|
||||
hiddenComponents.containsKey(i) -> hiddenComponents[i]!!
|
||||
else -> throw IllegalStateException("Missing component hashes")
|
||||
}
|
||||
}
|
||||
combinedHash(hashList)
|
||||
}
|
||||
override val outputs: List<TransactionState<ContractState>> get() = emptyList()
|
||||
|
||||
/** Contains the serialized component and the associated nonce for computing the transaction id. */
|
||||
@CordaSerializable
|
||||
class FilteredComponent(val component: OpaqueBytes, val nonce: SecureHash)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,7 +161,7 @@ data class ContractUpgradeLedgerTransaction(
|
||||
override val inputs: List<StateAndRef<ContractState>>,
|
||||
override val notary: Party,
|
||||
val legacyContractAttachment: Attachment,
|
||||
val upgradeContractClassName: ContractClassName,
|
||||
val upgradedContractClassName: ContractClassName,
|
||||
val upgradedContractAttachment: Attachment,
|
||||
override val id: SecureHash,
|
||||
val privacySalt: PrivacySalt,
|
||||
@ -165,7 +223,7 @@ data class ContractUpgradeLedgerTransaction(
|
||||
// TODO: re-map encumbrance pointers
|
||||
input.state.copy(
|
||||
data = upgradedState,
|
||||
contract = upgradeContractClassName,
|
||||
contract = upgradedContractClassName,
|
||||
constraint = outputConstraint
|
||||
)
|
||||
}
|
||||
@ -182,7 +240,7 @@ data class ContractUpgradeLedgerTransaction(
|
||||
private fun loadUpgradedContract(): UpgradedContract<ContractState, *> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return this::class.java.classLoader
|
||||
.loadClass(upgradeContractClassName)
|
||||
.loadClass(upgradedContractClassName)
|
||||
.asSubclass(Contract::class.java)
|
||||
.getConstructor()
|
||||
.newInstance() as UpgradedContract<ContractState, *>
|
||||
|
@ -3,11 +3,15 @@ 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.crypto.sha256
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction.Component.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import java.security.PublicKey
|
||||
|
||||
@ -18,10 +22,18 @@ import java.security.PublicKey
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NotaryChangeWireTransaction(
|
||||
override val inputs: List<StateRef>,
|
||||
override val notary: Party,
|
||||
val newNotary: Party
|
||||
/**
|
||||
* Contains all of the transaction components in serialized form.
|
||||
* This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties
|
||||
* may result in a different byte sequence depending on the serialization context.
|
||||
*/
|
||||
val serializedComponents: List<OpaqueBytes>
|
||||
) : CoreTransaction() {
|
||||
override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
|
||||
override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize()
|
||||
/** Identity of the notary service to reassign the states to.*/
|
||||
val newNotary: Party = serializedComponents[NEW_NOTARY.ordinal].deserialize()
|
||||
|
||||
/**
|
||||
* This transaction does not contain any output states, outputs can be obtained by resolving a
|
||||
* [NotaryChangeLedgerTransaction] and applying the notary modification to inputs.
|
||||
@ -39,16 +51,29 @@ data class NotaryChangeWireTransaction(
|
||||
* A privacy salt is not really required in this case, because we already used nonces in normal transactions and
|
||||
* thus input state refs will always be unique. Also, filtering doesn't apply on this type of transactions.
|
||||
*/
|
||||
override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) }
|
||||
override val id: SecureHash by lazy {
|
||||
serializedComponents.map { component ->
|
||||
component.bytes.sha256()
|
||||
}.reduce { combinedHash, componentHash ->
|
||||
combinedHash.hashConcat(componentHash)
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
|
||||
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>) : NotaryChangeLedgerTransaction {
|
||||
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
|
||||
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
|
||||
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)
|
||||
|
||||
enum class Component {
|
||||
INPUTS, NOTARY, NEW_NOTARY
|
||||
}
|
||||
|
||||
@Deprecated("Required only for backwards compatibility purposes. This type of transaction should not be constructed outside Corda code.", ReplaceWith("NotaryChangeTransactionBuilder"), DeprecationLevel.WARNING)
|
||||
constructor(inputs: List<StateRef>, notary: Party, newNotary: Party) : this(listOf(inputs, notary, newNotary).map { it.serialize() })
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,10 +22,7 @@ import net.corda.core.serialization.MissingAttachmentsException
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.transactions.*
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.toNonEmptySet
|
||||
import net.corda.nodeapi.internal.serialization.CordaClassResolver
|
||||
@ -129,6 +126,7 @@ object DefaultKryoCustomizer {
|
||||
register(java.lang.invoke.SerializedLambda::class.java)
|
||||
register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer)
|
||||
register(ContractUpgradeWireTransaction::class.java, ContractUpgradeWireTransactionSerializer)
|
||||
register(ContractUpgradeFilteredTransaction::class.java, ContractUpgradeFilteredTransactionSerializer)
|
||||
|
||||
for (whitelistProvider in serializationWhitelists) {
|
||||
val types = whitelistProvider.whitelist
|
||||
|
@ -8,14 +8,10 @@ import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
|
||||
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
||||
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint
|
||||
@ -25,6 +21,7 @@ import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.toObservable
|
||||
import net.corda.core.transactions.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.serialization.CordaClassResolver
|
||||
import net.corda.nodeapi.internal.serialization.serializationContextKey
|
||||
@ -254,40 +251,41 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
@ThreadSafe
|
||||
object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransaction>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) {
|
||||
kryo.writeClassAndObject(output, obj.inputs)
|
||||
kryo.writeClassAndObject(output, obj.notary)
|
||||
kryo.writeClassAndObject(output, obj.newNotary)
|
||||
kryo.writeClassAndObject(output, obj.serializedComponents)
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<NotaryChangeWireTransaction>): NotaryChangeWireTransaction {
|
||||
val inputs: List<StateRef> = uncheckedCast(kryo.readClassAndObject(input))
|
||||
val notary = kryo.readClassAndObject(input) as Party
|
||||
val newNotary = kryo.readClassAndObject(input) as Party
|
||||
|
||||
return NotaryChangeWireTransaction(inputs, notary, newNotary)
|
||||
val components : List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input))
|
||||
return NotaryChangeWireTransaction(components)
|
||||
}
|
||||
}
|
||||
|
||||
@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.serializedComponents)
|
||||
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 components: List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input))
|
||||
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
|
||||
|
||||
return ContractUpgradeWireTransaction(inputs, notary, legacyContractAttachment, upgradeContractClassName, upgradedContractAttachment, privacySalt)
|
||||
return ContractUpgradeWireTransaction(components, privacySalt)
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
object ContractUpgradeFilteredTransactionSerializer : Serializer<ContractUpgradeFilteredTransaction>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeFilteredTransaction) {
|
||||
kryo.writeClassAndObject(output, obj.visibleComponents)
|
||||
kryo.writeClassAndObject(output, obj.hiddenComponents)
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeFilteredTransaction>): ContractUpgradeFilteredTransaction {
|
||||
val visibleComponents: Map<Int, ContractUpgradeFilteredTransaction.FilteredComponent> = uncheckedCast(kryo.readClassAndObject(input))
|
||||
val hiddenComponents: Map<Int, SecureHash> = uncheckedCast(kryo.readClassAndObject(input))
|
||||
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,14 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import com.nhaarman.mockito_kotlin.argThat
|
||||
import com.nhaarman.mockito_kotlin.doNothing
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.NullKeys
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.internal.NotaryChangeTransactionBuilder
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.StatesNotAvailableException
|
||||
@ -17,7 +21,6 @@ import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.PageSpecification
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.QueryCriteria.*
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
@ -607,7 +610,7 @@ class NodeVaultServiceTest {
|
||||
// Change notary
|
||||
services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY)
|
||||
val newNotary = DUMMY_NOTARY
|
||||
val changeNotaryTx = NotaryChangeWireTransaction(listOf(initialCashState.ref), issueStx.notary!!, newNotary)
|
||||
val changeNotaryTx = NotaryChangeTransactionBuilder(listOf(initialCashState.ref), issueStx.notary!!, newNotary).build()
|
||||
val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0))
|
||||
|
||||
database.transaction {
|
||||
|
Loading…
x
Reference in New Issue
Block a user