mirror of
https://github.com/corda/corda.git
synced 2025-05-07 11:08:40 +00:00
LedgerTransaction no longer a data class to prevent more copy methods and fixed serialisation issue (CORDA-2231) (#4287)
LedgerTransaction is not meant to be created directly from client code, but it being a data class means we will expose new copy methods as new properties are added. The existing copy methods that we've exposed since V3 are deprecated, and equals and hashCode have been updated to be based just on id. The primary c'tor has been clearly marked as the class' wire format, and so the internal stuff has been moved out. The references property cannot be made nullable and so DeprecatedConstructorForDeserialization is used instead.
This commit is contained in:
parent
49d1cee6d4
commit
51adf9b678
@ -27,6 +27,7 @@ import net.corda.core.crypto.*
|
|||||||
import net.corda.core.crypto.PartialMerkleTree.PartialTree
|
import net.corda.core.crypto.PartialMerkleTree.PartialTree
|
||||||
import net.corda.core.identity.*
|
import net.corda.core.identity.*
|
||||||
import net.corda.core.internal.DigitalSignatureWithCert
|
import net.corda.core.internal.DigitalSignatureWithCert
|
||||||
|
import net.corda.core.internal.createComponentGroups
|
||||||
import net.corda.core.internal.kotlinObjectInstance
|
import net.corda.core.internal.kotlinObjectInstance
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
@ -187,6 +188,7 @@ private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
|
|||||||
value.commands,
|
value.commands,
|
||||||
value.timeWindow,
|
value.timeWindow,
|
||||||
value.attachments,
|
value.attachments,
|
||||||
|
value.references,
|
||||||
value.privacySalt
|
value.privacySalt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -195,13 +197,14 @@ private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
|
|||||||
private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>() {
|
private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>() {
|
||||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): WireTransaction {
|
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): WireTransaction {
|
||||||
val wrapper = parser.readValueAs<WireTransactionJson>()
|
val wrapper = parser.readValueAs<WireTransactionJson>()
|
||||||
val componentGroups = WireTransaction.createComponentGroups(
|
val componentGroups = createComponentGroups(
|
||||||
wrapper.inputs,
|
wrapper.inputs,
|
||||||
wrapper.outputs,
|
wrapper.outputs,
|
||||||
wrapper.commands,
|
wrapper.commands,
|
||||||
wrapper.attachments,
|
wrapper.attachments,
|
||||||
wrapper.notary,
|
wrapper.notary,
|
||||||
wrapper.timeWindow
|
wrapper.timeWindow,
|
||||||
|
wrapper.references
|
||||||
)
|
)
|
||||||
return WireTransaction(componentGroups, wrapper.privacySalt)
|
return WireTransaction(componentGroups, wrapper.privacySalt)
|
||||||
}
|
}
|
||||||
@ -214,6 +217,7 @@ private class WireTransactionJson(val id: SecureHash,
|
|||||||
val commands: List<Command<*>>,
|
val commands: List<Command<*>>,
|
||||||
val timeWindow: TimeWindow?,
|
val timeWindow: TimeWindow?,
|
||||||
val attachments: List<SecureHash>,
|
val attachments: List<SecureHash>,
|
||||||
|
val references: List<StateRef>,
|
||||||
val privacySalt: PrivacySalt)
|
val privacySalt: PrivacySalt)
|
||||||
|
|
||||||
private interface TransactionStateMixin {
|
private interface TransactionStateMixin {
|
||||||
|
@ -93,7 +93,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
|||||||
services = rigorousMock()
|
services = rigorousMock()
|
||||||
cordappProvider = rigorousMock()
|
cordappProvider = rigorousMock()
|
||||||
doReturn(cordappProvider).whenever(services).cordappProvider
|
doReturn(cordappProvider).whenever(services).cordappProvider
|
||||||
doReturn(testNetworkParameters()).whenever(services).networkParameters
|
doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters
|
||||||
doReturn(attachments).whenever(services).attachments
|
doReturn(attachments).whenever(services).attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,6 +245,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
|||||||
outputs = mutableListOf(createTransactionState()),
|
outputs = mutableListOf(createTransactionState()),
|
||||||
commands = mutableListOf(Command(DummyCommandData, listOf(BOB_PUBKEY))),
|
commands = mutableListOf(Command(DummyCommandData, listOf(BOB_PUBKEY))),
|
||||||
window = TimeWindow.fromStartAndDuration(Instant.now(), 1.hours),
|
window = TimeWindow.fromStartAndDuration(Instant.now(), 1.hours),
|
||||||
|
references = mutableListOf(StateRef(SecureHash.randomSHA256(), 0)),
|
||||||
privacySalt = net.corda.core.contracts.PrivacySalt()
|
privacySalt = net.corda.core.contracts.PrivacySalt()
|
||||||
).toWireTransaction(services)
|
).toWireTransaction(services)
|
||||||
val stx = sign(wtx)
|
val stx = sign(wtx)
|
||||||
@ -253,7 +254,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
|||||||
println(mapper.writeValueAsString(json))
|
println(mapper.writeValueAsString(json))
|
||||||
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
|
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
|
||||||
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs)
|
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs)
|
||||||
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "privacySalt")
|
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "references", "privacySalt")
|
||||||
assertThat(wtxFields[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
|
assertThat(wtxFields[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
|
||||||
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
|
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
|
||||||
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs)
|
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs)
|
||||||
@ -261,7 +262,8 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
|||||||
assertThat(wtxFields[4].childrenAs<TransactionState<*>>(mapper)).isEqualTo(wtx.outputs)
|
assertThat(wtxFields[4].childrenAs<TransactionState<*>>(mapper)).isEqualTo(wtx.outputs)
|
||||||
assertThat(wtxFields[5].childrenAs<Command<*>>(mapper)).isEqualTo(wtx.commands)
|
assertThat(wtxFields[5].childrenAs<Command<*>>(mapper)).isEqualTo(wtx.commands)
|
||||||
assertThat(wtxFields[6].valueAs<TimeWindow>(mapper)).isEqualTo(wtx.timeWindow)
|
assertThat(wtxFields[6].valueAs<TimeWindow>(mapper)).isEqualTo(wtx.timeWindow)
|
||||||
assertThat(wtxFields[7].valueAs<PrivacySalt>(mapper)).isEqualTo(wtx.privacySalt)
|
assertThat(wtxFields[7].childrenAs<StateRef>(mapper)).isEqualTo(wtx.references)
|
||||||
|
assertThat(wtxFields[8].valueAs<PrivacySalt>(mapper)).isEqualTo(wtx.privacySalt)
|
||||||
assertThat(mapper.convertValue<WireTransaction>(wtxJson)).isEqualTo(wtx)
|
assertThat(mapper.convertValue<WireTransaction>(wtxJson)).isEqualTo(wtx)
|
||||||
assertThat(mapper.convertValue<SignedTransaction>(json)).isEqualTo(stx)
|
assertThat(mapper.convertValue<SignedTransaction>(json)).isEqualTo(stx)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.KeepForDJVM
|
|
||||||
import net.corda.core.contracts.ContractState
|
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.contracts.TransactionState
|
|
||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.cordapp.CordappConfig
|
import net.corda.core.cordapp.CordappConfig
|
||||||
import net.corda.core.cordapp.CordappContext
|
import net.corda.core.cordapp.CordappContext
|
||||||
@ -12,14 +8,11 @@ import net.corda.core.crypto.SecureHash
|
|||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.ZoneVersionTooLowException
|
import net.corda.core.node.ZoneVersionTooLowException
|
||||||
import net.corda.core.serialization.CordaSerializable
|
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.serialization.SerializedBytes
|
|
||||||
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.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
|
|
||||||
// *Internal* Corda-specific utilities
|
// *Internal* Corda-specific utilities
|
||||||
@ -80,11 +73,3 @@ class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) ->
|
|||||||
override fun get(index: Int) = partialResolvedList[index]
|
override fun get(index: Int) = partialResolvedList[index]
|
||||||
?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed }
|
?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A SerializedStateAndRef is a pair (BinaryStateRepresentation, StateRef).
|
|
||||||
* The [serializedState] is the actual component from the original transaction.
|
|
||||||
*/
|
|
||||||
@KeepForDJVM
|
|
||||||
@CordaSerializable
|
|
||||||
data class SerializedStateAndRef(val serializedState: SerializedBytes<TransactionState<ContractState>>, val ref: StateRef)
|
|
@ -1,18 +1,13 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.componentHash
|
import net.corda.core.crypto.componentHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.MissingAttachmentsException
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.transactions.*
|
||||||
import net.corda.core.serialization.SerializationFactory
|
|
||||||
import net.corda.core.serialization.serialize
|
|
||||||
import net.corda.core.transactions.ComponentGroup
|
|
||||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
|
||||||
import net.corda.core.transactions.FilteredComponentGroup
|
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.lazyMapped
|
import net.corda.core.utilities.lazyMapped
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@ -121,4 +116,40 @@ fun deserialiseCommands(componentGroups: List<ComponentGroup>,
|
|||||||
}
|
}
|
||||||
commandDataList.lazyMapped { commandData, index -> Command(commandData, signersList[index]) }
|
commandDataList.lazyMapped { commandData, index -> Command(commandData, signersList[index]) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creating list of [ComponentGroup] used in one of the constructors of [WireTransaction] required
|
||||||
|
* for backwards compatibility purposes.
|
||||||
|
*/
|
||||||
|
fun createComponentGroups(inputs: List<StateRef>,
|
||||||
|
outputs: List<TransactionState<ContractState>>,
|
||||||
|
commands: List<Command<*>>,
|
||||||
|
attachments: List<SecureHash>,
|
||||||
|
notary: Party?,
|
||||||
|
timeWindow: TimeWindow?,
|
||||||
|
references: List<StateRef>): List<ComponentGroup> {
|
||||||
|
val serialize = { value: Any, _: Int -> value.serialize() }
|
||||||
|
val componentGroupMap: MutableList<ComponentGroup> = mutableListOf()
|
||||||
|
if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.INPUTS_GROUP.ordinal, inputs.lazyMapped(serialize)))
|
||||||
|
if (references.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.REFERENCES_GROUP.ordinal, references.lazyMapped(serialize)))
|
||||||
|
if (outputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP.ordinal, outputs.lazyMapped(serialize)))
|
||||||
|
// Adding commandData only to the commands group. Signers are added in their own group.
|
||||||
|
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.COMMANDS_GROUP.ordinal, commands.map { it.value }.lazyMapped(serialize)))
|
||||||
|
if (attachments.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, attachments.lazyMapped(serialize)))
|
||||||
|
if (notary != null) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.NOTARY_GROUP.ordinal, listOf(notary).lazyMapped(serialize)))
|
||||||
|
if (timeWindow != null) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, listOf(timeWindow).lazyMapped(serialize)))
|
||||||
|
// Adding signers to their own group. This is required for command visibility purposes: a party receiving
|
||||||
|
// a FilteredTransaction can now verify it sees all the commands it should sign.
|
||||||
|
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.SIGNERS_GROUP.ordinal, commands.map { it.signers }.lazyMapped(serialize)))
|
||||||
|
return componentGroupMap
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SerializedStateAndRef is a pair (BinaryStateRepresentation, StateRef).
|
||||||
|
* The [serializedState] is the actual component from the original wire transaction.
|
||||||
|
*/
|
||||||
|
@KeepForDJVM
|
||||||
|
data class SerializedStateAndRef(val serializedState: SerializedBytes<TransactionState<ContractState>>, val ref: StateRef) {
|
||||||
|
fun toStateAndRef(): StateAndRef<ContractState> = StateAndRef(serializedState.deserialize(), ref)
|
||||||
|
}
|
||||||
|
@ -8,10 +8,11 @@ import net.corda.core.crypto.isFulfilledBy
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
|
import net.corda.core.serialization.ConstructorForDeserialization
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
|
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.warnOnce
|
import net.corda.core.utilities.warnOnce
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
@ -28,12 +29,13 @@ import kotlin.collections.HashSet
|
|||||||
*
|
*
|
||||||
* All the above refer to inputs using a (txhash, output index) pair.
|
* All the above refer to inputs using a (txhash, output index) pair.
|
||||||
*/
|
*/
|
||||||
// TODO LedgerTransaction is not supposed to be serialisable as it references attachments, etc. The verification logic
|
|
||||||
// currently sends this across to out-of-process verifiers. We'll need to change that first.
|
|
||||||
// DOCSTART 1
|
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class LedgerTransaction private constructor(
|
class LedgerTransaction
|
||||||
|
@ConstructorForDeserialization
|
||||||
|
// LedgerTransaction is not meant to be created directly from client code, but rather via WireTransaction.toLedgerTransaction
|
||||||
|
private constructor(
|
||||||
|
// DOCSTART 1
|
||||||
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
|
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
|
||||||
override val inputs: List<StateAndRef<ContractState>>,
|
override val inputs: List<StateAndRef<ContractState>>,
|
||||||
override val outputs: List<TransactionState<ContractState>>,
|
override val outputs: List<TransactionState<ContractState>>,
|
||||||
@ -46,39 +48,15 @@ data class LedgerTransaction private constructor(
|
|||||||
override val notary: Party?,
|
override val notary: Party?,
|
||||||
val timeWindow: TimeWindow?,
|
val timeWindow: TimeWindow?,
|
||||||
val privacySalt: PrivacySalt,
|
val privacySalt: PrivacySalt,
|
||||||
private val networkParameters: NetworkParameters?,
|
val networkParameters: NetworkParameters?,
|
||||||
override val references: List<StateAndRef<ContractState>>,
|
override val references: List<StateAndRef<ContractState>>
|
||||||
val componentGroups: List<ComponentGroup>?,
|
//DOCEND 1
|
||||||
val resolvedInputBytes: List<SerializedStateAndRef>?,
|
|
||||||
val resolvedReferenceBytes: List<SerializedStateAndRef>?
|
|
||||||
) : FullTransaction() {
|
) : FullTransaction() {
|
||||||
|
// These are not part of the c'tor above as that defines LedgerTransaction's serialisation format
|
||||||
|
private var componentGroups: List<ComponentGroup>? = null
|
||||||
|
private var serializedInputs: List<SerializedStateAndRef>? = null
|
||||||
|
private var serializedReferences: List<SerializedStateAndRef>? = null
|
||||||
|
|
||||||
@Deprecated("Client code should not instantiate LedgerTransaction.")
|
|
||||||
constructor(
|
|
||||||
inputs: List<StateAndRef<ContractState>>,
|
|
||||||
outputs: List<TransactionState<ContractState>>,
|
|
||||||
commands: List<CommandWithParties<CommandData>>,
|
|
||||||
attachments: List<Attachment>,
|
|
||||||
id: SecureHash,
|
|
||||||
notary: Party?,
|
|
||||||
timeWindow: TimeWindow?,
|
|
||||||
privacySalt: PrivacySalt
|
|
||||||
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null, emptyList(), null, null, null)
|
|
||||||
|
|
||||||
@Deprecated("Client code should not instantiate LedgerTransaction.")
|
|
||||||
constructor(
|
|
||||||
inputs: List<StateAndRef<ContractState>>,
|
|
||||||
outputs: List<TransactionState<ContractState>>,
|
|
||||||
commands: List<CommandWithParties<CommandData>>,
|
|
||||||
attachments: List<Attachment>,
|
|
||||||
id: SecureHash,
|
|
||||||
notary: Party?,
|
|
||||||
timeWindow: TimeWindow?,
|
|
||||||
privacySalt: PrivacySalt,
|
|
||||||
networkParameters: NetworkParameters?
|
|
||||||
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList(), null, null, null)
|
|
||||||
|
|
||||||
//DOCEND 1
|
|
||||||
init {
|
init {
|
||||||
checkBaseInvariants()
|
checkBaseInvariants()
|
||||||
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
||||||
@ -87,10 +65,10 @@ data class LedgerTransaction private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<LedgerTransaction>()
|
private val logger = contextLogger()
|
||||||
|
|
||||||
@CordaInternal
|
@CordaInternal
|
||||||
internal fun makeLedgerTransaction(
|
internal fun create(
|
||||||
inputs: List<StateAndRef<ContractState>>,
|
inputs: List<StateAndRef<ContractState>>,
|
||||||
outputs: List<TransactionState<ContractState>>,
|
outputs: List<TransactionState<ContractState>>,
|
||||||
commands: List<CommandWithParties<CommandData>>,
|
commands: List<CommandWithParties<CommandData>>,
|
||||||
@ -101,10 +79,16 @@ data class LedgerTransaction private constructor(
|
|||||||
privacySalt: PrivacySalt,
|
privacySalt: PrivacySalt,
|
||||||
networkParameters: NetworkParameters?,
|
networkParameters: NetworkParameters?,
|
||||||
references: List<StateAndRef<ContractState>>,
|
references: List<StateAndRef<ContractState>>,
|
||||||
componentGroups: List<ComponentGroup>,
|
componentGroups: List<ComponentGroup>? = null,
|
||||||
resolvedInputBytes: List<SerializedStateAndRef>,
|
serializedInputs: List<SerializedStateAndRef>? = null,
|
||||||
resolvedReferenceBytes: List<SerializedStateAndRef>
|
serializedReferences: List<SerializedStateAndRef>? = null
|
||||||
) = LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, resolvedInputBytes, resolvedReferenceBytes)
|
): LedgerTransaction {
|
||||||
|
return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references).apply {
|
||||||
|
this.componentGroups = componentGroups
|
||||||
|
this.serializedInputs = serializedInputs
|
||||||
|
this.serializedReferences = serializedReferences
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val inputStates: List<ContractState> get() = inputs.map { it.state.data }
|
val inputStates: List<ContractState> get() = inputs.map { it.state.data }
|
||||||
@ -137,7 +121,7 @@ data class LedgerTransaction private constructor(
|
|||||||
|
|
||||||
AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader ->
|
AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader ->
|
||||||
|
|
||||||
val internalTx = createInternalLedgerTransaction()
|
val internalTx = createLtxForVerification()
|
||||||
|
|
||||||
// TODO - verify for version downgrade
|
// TODO - verify for version downgrade
|
||||||
validatePackageOwnership(contractAttachmentsByContract)
|
validatePackageOwnership(contractAttachmentsByContract)
|
||||||
@ -195,7 +179,9 @@ data class LedgerTransaction private constructor(
|
|||||||
* * Constraints should be one of the valid supported ones.
|
* * Constraints should be one of the valid supported ones.
|
||||||
* * Constraints should propagate correctly if not marked otherwise.
|
* * Constraints should propagate correctly if not marked otherwise.
|
||||||
*/
|
*/
|
||||||
private fun verifyConstraintsValidity(internalTx: LedgerTransaction, contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>, transactionClassLoader: ClassLoader) {
|
private fun verifyConstraintsValidity(internalTx: LedgerTransaction,
|
||||||
|
contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>,
|
||||||
|
transactionClassLoader: ClassLoader) {
|
||||||
// First check that the constraints are valid.
|
// First check that the constraints are valid.
|
||||||
for (state in internalTx.allStates) {
|
for (state in internalTx.allStates) {
|
||||||
checkConstraintValidity(state)
|
checkConstraintValidity(state)
|
||||||
@ -277,30 +263,38 @@ data class LedgerTransaction private constructor(
|
|||||||
throw TransactionVerificationException.ContractCreationError(id, className, e)
|
throw TransactionVerificationException.ContractCreationError(id, className, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createInternalLedgerTransaction(): LedgerTransaction {
|
private fun createLtxForVerification(): LedgerTransaction {
|
||||||
return if (resolvedInputBytes != null && resolvedReferenceBytes != null && componentGroups != null) {
|
val serializedInputs = this.serializedInputs
|
||||||
|
val serializedReferences = this.serializedReferences
|
||||||
|
val componentGroups = this.componentGroups
|
||||||
|
|
||||||
|
return if (serializedInputs != null && serializedReferences != null && componentGroups != null) {
|
||||||
// Deserialize all relevant classes in the transaction classloader.
|
// Deserialize all relevant classes in the transaction classloader.
|
||||||
val resolvedDeserializedInputs = resolvedInputBytes.map { StateAndRef(it.serializedState.deserialize(), it.ref) }
|
val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
|
||||||
val resolvedDeserializedReferences = resolvedReferenceBytes.map { StateAndRef(it.serializedState.deserialize(), it.ref) }
|
val deserializedReferences = serializedReferences.map { it.toStateAndRef() }
|
||||||
val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
|
val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
|
||||||
val deserializedCommands = deserialiseCommands(this.componentGroups, forceDeserialize = true)
|
val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true)
|
||||||
val authenticatedArgs = deserializedCommands.map { cmd ->
|
val authenticatedDeserializedCommands = deserializedCommands.map { cmd ->
|
||||||
val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties
|
val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties
|
||||||
CommandWithParties(cmd.signers, parties, cmd.value)
|
CommandWithParties(cmd.signers, parties, cmd.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
val ledgerTransactionToVerify = this.copy(
|
LedgerTransaction(
|
||||||
inputs = resolvedDeserializedInputs,
|
inputs = deserializedInputs,
|
||||||
outputs = deserializedOutputs,
|
outputs = deserializedOutputs,
|
||||||
commands = authenticatedArgs,
|
commands = authenticatedDeserializedCommands,
|
||||||
references = resolvedDeserializedReferences)
|
attachments = this.attachments,
|
||||||
|
id = this.id,
|
||||||
ledgerTransactionToVerify
|
notary = this.notary,
|
||||||
|
timeWindow = this.timeWindow,
|
||||||
|
privacySalt = this.privacySalt,
|
||||||
|
networkParameters = this.networkParameters,
|
||||||
|
references = deserializedReferences
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// This branch is only present for backwards compatibility.
|
// This branch is only present for backwards compatibility.
|
||||||
// TODO - it should be removed once the constructor of LedgerTransaction is no longer public api.
|
logger.warn("The LedgerTransaction should not be instantiated directly from client code. Please use WireTransaction.toLedgerTransaction." +
|
||||||
logger.warn("The LedgerTransaction should not be instantiated directly from client code. Please use WireTransaction.toLedgerTransaction. The result of the verify method might not be accurate.")
|
"The result of the verify method might not be accurate.")
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -766,7 +760,92 @@ data class LedgerTransaction private constructor(
|
|||||||
*/
|
*/
|
||||||
fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id }
|
fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id }
|
||||||
|
|
||||||
@JvmOverloads
|
operator fun component1(): List<StateAndRef<ContractState>> = inputs
|
||||||
|
operator fun component2(): List<TransactionState<ContractState>> = outputs
|
||||||
|
operator fun component3(): List<CommandWithParties<CommandData>> = commands
|
||||||
|
operator fun component4(): List<Attachment> = attachments
|
||||||
|
operator fun component5(): SecureHash = id
|
||||||
|
operator fun component6(): Party? = notary
|
||||||
|
operator fun component7(): TimeWindow? = timeWindow
|
||||||
|
operator fun component8(): PrivacySalt = privacySalt
|
||||||
|
operator fun component9(): NetworkParameters? = networkParameters
|
||||||
|
operator fun component10(): List<StateAndRef<ContractState>> = references
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean = this === other || other is LedgerTransaction && this.id == other.id
|
||||||
|
|
||||||
|
override fun hashCode(): Int = id.hashCode()
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return """LedgerTransaction(
|
||||||
|
| id=$id
|
||||||
|
| inputs=$inputs
|
||||||
|
| outputs=$outputs
|
||||||
|
| commands=$commands
|
||||||
|
| attachments=$attachments
|
||||||
|
| notary=$notary
|
||||||
|
| timeWindow=$timeWindow
|
||||||
|
| references=$references
|
||||||
|
| networkParameters=$networkParameters
|
||||||
|
| privacySalt=$privacySalt
|
||||||
|
|)""".trimMargin()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Stuff that we can't remove and so is deprecated instead
|
||||||
|
//
|
||||||
|
|
||||||
|
@Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||||
|
constructor(
|
||||||
|
inputs: List<StateAndRef<ContractState>>,
|
||||||
|
outputs: List<TransactionState<ContractState>>,
|
||||||
|
commands: List<CommandWithParties<CommandData>>,
|
||||||
|
attachments: List<Attachment>,
|
||||||
|
id: SecureHash,
|
||||||
|
notary: Party?,
|
||||||
|
timeWindow: TimeWindow?,
|
||||||
|
privacySalt: PrivacySalt
|
||||||
|
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null, emptyList())
|
||||||
|
|
||||||
|
@Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||||
|
@DeprecatedConstructorForDeserialization(1)
|
||||||
|
constructor(
|
||||||
|
inputs: List<StateAndRef<ContractState>>,
|
||||||
|
outputs: List<TransactionState<ContractState>>,
|
||||||
|
commands: List<CommandWithParties<CommandData>>,
|
||||||
|
attachments: List<Attachment>,
|
||||||
|
id: SecureHash,
|
||||||
|
notary: Party?,
|
||||||
|
timeWindow: TimeWindow?,
|
||||||
|
privacySalt: PrivacySalt,
|
||||||
|
networkParameters: NetworkParameters?
|
||||||
|
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList())
|
||||||
|
|
||||||
|
@Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||||
|
fun copy(inputs: List<StateAndRef<ContractState>>,
|
||||||
|
outputs: List<TransactionState<ContractState>>,
|
||||||
|
commands: List<CommandWithParties<CommandData>>,
|
||||||
|
attachments: List<Attachment>,
|
||||||
|
id: SecureHash,
|
||||||
|
notary: Party?,
|
||||||
|
timeWindow: TimeWindow?,
|
||||||
|
privacySalt: PrivacySalt
|
||||||
|
): LedgerTransaction {
|
||||||
|
return LedgerTransaction(
|
||||||
|
inputs = inputs,
|
||||||
|
outputs = outputs,
|
||||||
|
commands = commands,
|
||||||
|
attachments = attachments,
|
||||||
|
id = id,
|
||||||
|
notary = notary,
|
||||||
|
timeWindow = timeWindow,
|
||||||
|
privacySalt = privacySalt,
|
||||||
|
networkParameters = networkParameters,
|
||||||
|
references = references
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||||
fun copy(inputs: List<StateAndRef<ContractState>> = this.inputs,
|
fun copy(inputs: List<StateAndRef<ContractState>> = this.inputs,
|
||||||
outputs: List<TransactionState<ContractState>> = this.outputs,
|
outputs: List<TransactionState<ContractState>> = this.outputs,
|
||||||
commands: List<CommandWithParties<CommandData>> = this.commands,
|
commands: List<CommandWithParties<CommandData>> = this.commands,
|
||||||
@ -776,16 +855,18 @@ data class LedgerTransaction private constructor(
|
|||||||
timeWindow: TimeWindow? = this.timeWindow,
|
timeWindow: TimeWindow? = this.timeWindow,
|
||||||
privacySalt: PrivacySalt = this.privacySalt,
|
privacySalt: PrivacySalt = this.privacySalt,
|
||||||
networkParameters: NetworkParameters? = this.networkParameters
|
networkParameters: NetworkParameters? = this.networkParameters
|
||||||
) = copy(inputs = inputs,
|
): LedgerTransaction {
|
||||||
outputs = outputs,
|
return LedgerTransaction(
|
||||||
commands = commands,
|
inputs = inputs,
|
||||||
attachments = attachments,
|
outputs = outputs,
|
||||||
id = id,
|
commands = commands,
|
||||||
notary = notary,
|
attachments = attachments,
|
||||||
timeWindow = timeWindow,
|
id = id,
|
||||||
privacySalt = privacySalt,
|
notary = notary,
|
||||||
networkParameters = networkParameters,
|
timeWindow = timeWindow,
|
||||||
references = references
|
privacySalt = privacySalt,
|
||||||
)
|
networkParameters = networkParameters,
|
||||||
|
references = references
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,7 @@ import net.corda.core.DeleteForDJVM
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.AttachmentWithContext
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.internal.FlowStateMachine
|
|
||||||
import net.corda.core.internal.StatePointerSearch
|
|
||||||
import net.corda.core.internal.ensureMinimumPlatformVersion
|
|
||||||
import net.corda.core.internal.isUploaderTrusted
|
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
@ -127,14 +123,15 @@ open class TransactionBuilder @JvmOverloads constructor(
|
|||||||
|
|
||||||
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
|
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
|
||||||
WireTransaction(
|
WireTransaction(
|
||||||
WireTransaction.createComponentGroups(
|
createComponentGroups(
|
||||||
inputStates(),
|
inputStates(),
|
||||||
resolvedOutputs,
|
resolvedOutputs,
|
||||||
commands,
|
commands,
|
||||||
(allContractAttachments + attachments).toSortedSet().toList(), // Sort the attachments to ensure transaction builds are stable.
|
(allContractAttachments + attachments).toSortedSet().toList(), // Sort the attachments to ensure transaction builds are stable.
|
||||||
notary,
|
notary,
|
||||||
window,
|
window,
|
||||||
referenceStates),
|
referenceStates
|
||||||
|
),
|
||||||
privacySalt
|
privacySalt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,19 @@ import net.corda.core.CordaInternal
|
|||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.ComponentGroupEnum.*
|
import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP
|
||||||
|
import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.SerializedStateAndRef
|
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
|
import net.corda.core.internal.SerializedStateAndRef
|
||||||
|
import net.corda.core.internal.createComponentGroups
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
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.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.lazyMapped
|
import net.corda.core.utilities.lazyMapped
|
||||||
@ -53,7 +54,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, PrivacySalt())
|
constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, PrivacySalt())
|
||||||
|
|
||||||
@Deprecated("Required only in some unit-tests and for backwards compatibility purposes.", ReplaceWith("WireTransaction(val componentGroups: List<ComponentGroup>, override val privacySalt: PrivacySalt)"), DeprecationLevel.WARNING)
|
@Deprecated("Required only in some unit-tests and for backwards compatibility purposes.",
|
||||||
|
ReplaceWith("WireTransaction(val componentGroups: List<ComponentGroup>, override val privacySalt: PrivacySalt)"), DeprecationLevel.WARNING)
|
||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(
|
constructor(
|
||||||
@ -64,7 +66,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
notary: Party?,
|
notary: Party?,
|
||||||
timeWindow: TimeWindow?,
|
timeWindow: TimeWindow?,
|
||||||
privacySalt: PrivacySalt = PrivacySalt()
|
privacySalt: PrivacySalt = PrivacySalt()
|
||||||
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow), privacySalt)
|
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList()), privacySalt)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" }
|
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" }
|
||||||
@ -103,7 +105,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
return toLedgerTransactionInternal(
|
return toLedgerTransactionInternal(
|
||||||
resolveIdentity = { services.identityService.partyFromKey(it) },
|
resolveIdentity = { services.identityService.partyFromKey(it) },
|
||||||
resolveAttachment = { services.attachments.openAttachment(it) },
|
resolveAttachment = { services.attachments.openAttachment(it) },
|
||||||
resolveStateRefComponent = { resolveStateRefBinaryComponent(it, services) },
|
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
|
||||||
networkParameters = services.networkParameters
|
networkParameters = services.networkParameters
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -130,38 +132,44 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
private fun toLedgerTransactionInternal(
|
private fun toLedgerTransactionInternal(
|
||||||
resolveIdentity: (PublicKey) -> Party?,
|
resolveIdentity: (PublicKey) -> Party?,
|
||||||
resolveAttachment: (SecureHash) -> Attachment?,
|
resolveAttachment: (SecureHash) -> Attachment?,
|
||||||
resolveStateRefComponent: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
|
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
|
||||||
networkParameters: NetworkParameters?
|
networkParameters: NetworkParameters?
|
||||||
): LedgerTransaction {
|
): LedgerTransaction {
|
||||||
// Look up public keys to authenticated identities.
|
// Look up public keys to authenticated identities.
|
||||||
val authenticatedArgs = commands.lazyMapped { cmd, _ ->
|
val authenticatedCommands = commands.lazyMapped { cmd, _ ->
|
||||||
val parties = cmd.signers.mapNotNull { pk -> resolveIdentity(pk) }
|
val parties = cmd.signers.mapNotNull { pk -> resolveIdentity(pk) }
|
||||||
CommandWithParties(cmd.signers, parties, cmd.value)
|
CommandWithParties(cmd.signers, parties, cmd.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
val resolvedInputBytes = inputs.map { ref ->
|
val serializedResolvedInputs = inputs.map { ref ->
|
||||||
SerializedStateAndRef(resolveStateRefComponent(ref)
|
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
|
||||||
?: throw TransactionResolutionException(ref.txhash), ref)
|
|
||||||
}
|
|
||||||
val resolvedInputs = resolvedInputBytes.lazyMapped { (serialized, ref), _ ->
|
|
||||||
StateAndRef(serialized.deserialize(), ref)
|
|
||||||
}
|
}
|
||||||
|
val resolvedInputs = serializedResolvedInputs.lazyMapped { star, _ -> star.toStateAndRef() }
|
||||||
|
|
||||||
val resolvedReferenceBytes = references.map { ref ->
|
val serializedResolvedReferences = references.map { ref ->
|
||||||
SerializedStateAndRef(resolveStateRefComponent(ref)
|
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
|
||||||
?: throw TransactionResolutionException(ref.txhash), ref)
|
|
||||||
}
|
|
||||||
val resolvedReferences = resolvedReferenceBytes.lazyMapped { (serialized, ref), _ ->
|
|
||||||
StateAndRef(serialized.deserialize(), ref)
|
|
||||||
}
|
}
|
||||||
|
val resolvedReferences = serializedResolvedReferences.lazyMapped { star, _ -> star.toStateAndRef() }
|
||||||
|
|
||||||
val attachments = attachments.lazyMapped { att, _ ->
|
val resolvedAttachments = attachments.lazyMapped { att, _ -> resolveAttachment(att) ?: throw AttachmentResolutionException(att) }
|
||||||
resolveAttachment(att) ?: throw AttachmentResolutionException(att)
|
|
||||||
}
|
|
||||||
|
|
||||||
val ltx = LedgerTransaction.makeLedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters, resolvedReferences, componentGroups, resolvedInputBytes, resolvedReferenceBytes)
|
val ltx = LedgerTransaction.create(
|
||||||
|
resolvedInputs,
|
||||||
|
outputs,
|
||||||
|
authenticatedCommands,
|
||||||
|
resolvedAttachments,
|
||||||
|
id,
|
||||||
|
notary,
|
||||||
|
timeWindow,
|
||||||
|
privacySalt,
|
||||||
|
networkParameters,
|
||||||
|
resolvedReferences,
|
||||||
|
componentGroups,
|
||||||
|
serializedResolvedInputs,
|
||||||
|
serializedResolvedReferences
|
||||||
|
)
|
||||||
|
|
||||||
checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: DEFAULT_MAX_TX_SIZE)
|
checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: DEFAULT_MAX_TX_SIZE, serializedResolvedInputs, serializedResolvedReferences)
|
||||||
|
|
||||||
return ltx
|
return ltx
|
||||||
}
|
}
|
||||||
@ -170,7 +178,10 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
* Deterministic function that checks if the transaction is below the maximum allowed size.
|
* Deterministic function that checks if the transaction is below the maximum allowed size.
|
||||||
* It uses the binary representation of transactions.
|
* It uses the binary representation of transactions.
|
||||||
*/
|
*/
|
||||||
private fun checkTransactionSize(ltx: LedgerTransaction, maxTransactionSize: Int) {
|
private fun checkTransactionSize(ltx: LedgerTransaction,
|
||||||
|
maxTransactionSize: Int,
|
||||||
|
resolvedSerializedInputs: List<SerializedStateAndRef>,
|
||||||
|
resolvedSerializedReferences: List<SerializedStateAndRef>) {
|
||||||
var remainingTransactionSize = maxTransactionSize
|
var remainingTransactionSize = maxTransactionSize
|
||||||
|
|
||||||
fun minus(size: Int) {
|
fun minus(size: Int) {
|
||||||
@ -187,8 +198,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
// it's likely that the same underlying Attachment CorDapp will occur more than once so we dedup on the attachment id.
|
// it's likely that the same underlying Attachment CorDapp will occur more than once so we dedup on the attachment id.
|
||||||
ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) }
|
ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) }
|
||||||
|
|
||||||
minus(ltx.resolvedInputBytes!!.sumBy { it.serializedState.size })
|
minus(resolvedSerializedInputs.sumBy { it.serializedState.size })
|
||||||
minus(ltx.resolvedReferenceBytes!!.sumBy { it.serializedState.size })
|
minus(resolvedSerializedReferences.sumBy { it.serializedState.size })
|
||||||
|
|
||||||
// For Commands and outputs we can use the component groups as they are already serialized.
|
// For Commands and outputs we can use the component groups as they are already serialized.
|
||||||
minus(componentGroupSize(COMMANDS_GROUP))
|
minus(componentGroupSize(COMMANDS_GROUP))
|
||||||
@ -277,33 +288,15 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
companion object {
|
companion object {
|
||||||
private const val DEFAULT_MAX_TX_SIZE = 10485760
|
private const val DEFAULT_MAX_TX_SIZE = 10485760
|
||||||
|
|
||||||
/**
|
|
||||||
* Creating list of [ComponentGroup] used in one of the constructors of [WireTransaction] required
|
|
||||||
* for backwards compatibility purposes.
|
|
||||||
*/
|
|
||||||
@JvmOverloads
|
|
||||||
@CordaInternal
|
@CordaInternal
|
||||||
|
@Deprecated("Do not use, this is internal API")
|
||||||
fun createComponentGroups(inputs: List<StateRef>,
|
fun createComponentGroups(inputs: List<StateRef>,
|
||||||
outputs: List<TransactionState<ContractState>>,
|
outputs: List<TransactionState<ContractState>>,
|
||||||
commands: List<Command<*>>,
|
commands: List<Command<*>>,
|
||||||
attachments: List<SecureHash>,
|
attachments: List<SecureHash>,
|
||||||
notary: Party?,
|
notary: Party?,
|
||||||
timeWindow: TimeWindow?,
|
timeWindow: TimeWindow?): List<ComponentGroup> {
|
||||||
references: List<StateRef> = emptyList()): List<ComponentGroup> {
|
return createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList())
|
||||||
val serialize = { value: Any, _: Int -> value.serialize() }
|
|
||||||
val componentGroupMap: MutableList<ComponentGroup> = mutableListOf()
|
|
||||||
if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(INPUTS_GROUP.ordinal, inputs.lazyMapped(serialize)))
|
|
||||||
if (references.isNotEmpty()) componentGroupMap.add(ComponentGroup(REFERENCES_GROUP.ordinal, references.lazyMapped(serialize)))
|
|
||||||
if (outputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.lazyMapped(serialize)))
|
|
||||||
// Adding commandData only to the commands group. Signers are added in their own group.
|
|
||||||
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value }.lazyMapped(serialize)))
|
|
||||||
if (attachments.isNotEmpty()) componentGroupMap.add(ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.lazyMapped(serialize)))
|
|
||||||
if (notary != null) componentGroupMap.add(ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary).lazyMapped(serialize)))
|
|
||||||
if (timeWindow != null) componentGroupMap.add(ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow).lazyMapped(serialize)))
|
|
||||||
// Adding signers to their own group. This is required for command visibility purposes: a party receiving
|
|
||||||
// a FilteredTransaction can now verify it sees all the commands it should sign.
|
|
||||||
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers }.lazyMapped(serialize)))
|
|
||||||
return componentGroupMap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -312,7 +305,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
* For [ContractUpgradeWireTransaction] or [NotaryChangeWireTransaction] it knows how to recreate the output state in the correct classloader independent of the node's classpath.
|
* For [ContractUpgradeWireTransaction] or [NotaryChangeWireTransaction] it knows how to recreate the output state in the correct classloader independent of the node's classpath.
|
||||||
*/
|
*/
|
||||||
@CordaInternal
|
@CordaInternal
|
||||||
fun resolveStateRefBinaryComponent(stateRef: StateRef, services: ServicesForResolution): SerializedBytes<TransactionState<ContractState>>? {
|
internal fun resolveStateRefBinaryComponent(stateRef: StateRef, services: ServicesForResolution): SerializedBytes<TransactionState<ContractState>>? {
|
||||||
return if (services is ServiceHub) {
|
return if (services is ServiceHub) {
|
||||||
val coreTransaction = services.validatedTransactions.getTransaction(stateRef.txhash)?.coreTransaction
|
val coreTransaction = services.validatedTransactions.getTransaction(stateRef.txhash)?.coreTransaction
|
||||||
?: throw TransactionResolutionException(stateRef.txhash)
|
?: throw TransactionResolutionException(stateRef.txhash)
|
||||||
@ -333,11 +326,26 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val buf = StringBuilder()
|
val buf = StringBuilder()
|
||||||
buf.appendln("Transaction:")
|
buf.appendln("Transaction:")
|
||||||
for (reference in references) buf.appendln("${Emoji.rightArrow}REFS: $reference")
|
for (reference in references) {
|
||||||
for (input in inputs) buf.appendln("${Emoji.rightArrow}INPUT: $input")
|
val emoji = Emoji.rightArrow
|
||||||
for ((data) in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: $data")
|
buf.appendln("${emoji}REFS: $reference")
|
||||||
for (command in commands) buf.appendln("${Emoji.diamond}COMMAND: $command")
|
}
|
||||||
for (attachment in attachments) buf.appendln("${Emoji.paperclip}ATTACHMENT: $attachment")
|
for (input in inputs) {
|
||||||
|
val emoji = Emoji.rightArrow
|
||||||
|
buf.appendln("${emoji}INPUT: $input")
|
||||||
|
}
|
||||||
|
for ((data) in outputs) {
|
||||||
|
val emoji = Emoji.leftArrow
|
||||||
|
buf.appendln("${emoji}OUTPUT: $data")
|
||||||
|
}
|
||||||
|
for (command in commands) {
|
||||||
|
val emoji = Emoji.diamond
|
||||||
|
buf.appendln("${emoji}COMMAND: $command")
|
||||||
|
}
|
||||||
|
for (attachment in attachments) {
|
||||||
|
val emoji = Emoji.paperclip
|
||||||
|
buf.appendln("${emoji}ATTACHMENT: $attachment")
|
||||||
|
}
|
||||||
return buf.toString()
|
return buf.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.core.transactions
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.ComponentGroupEnum.*
|
import net.corda.core.contracts.ComponentGroupEnum.*
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
|
import net.corda.core.internal.createComponentGroups
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
@ -124,7 +125,7 @@ class CompatibleTransactionTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `WireTransaction constructors and compatibility`() {
|
fun `WireTransaction constructors and compatibility`() {
|
||||||
val groups = WireTransaction.createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow)
|
val groups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList())
|
||||||
val wireTransactionOldConstructor = WireTransaction(groups, privacySalt)
|
val wireTransactionOldConstructor = WireTransaction(groups, privacySalt)
|
||||||
assertEquals(wireTransactionA, wireTransactionOldConstructor)
|
assertEquals(wireTransactionA, wireTransactionOldConstructor)
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ class TransactionTests {
|
|||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
val privacySalt = PrivacySalt()
|
val privacySalt = PrivacySalt()
|
||||||
val transaction = LedgerTransaction(
|
val transaction = LedgerTransaction.create(
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
commands,
|
commands,
|
||||||
@ -133,7 +133,8 @@ class TransactionTests {
|
|||||||
null,
|
null,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
privacySalt,
|
privacySalt,
|
||||||
testNetworkParameters()
|
testNetworkParameters(),
|
||||||
|
emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
transaction.verify()
|
transaction.verify()
|
||||||
@ -166,7 +167,7 @@ class TransactionTests {
|
|||||||
val id = SecureHash.randomSHA256()
|
val id = SecureHash.randomSHA256()
|
||||||
val timeWindow: TimeWindow? = null
|
val timeWindow: TimeWindow? = null
|
||||||
val privacySalt = PrivacySalt()
|
val privacySalt = PrivacySalt()
|
||||||
fun buildTransaction() = LedgerTransaction(
|
fun buildTransaction() = LedgerTransaction.create(
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
commands,
|
commands,
|
||||||
@ -174,7 +175,9 @@ class TransactionTests {
|
|||||||
id,
|
id,
|
||||||
notary,
|
notary,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
privacySalt
|
privacySalt,
|
||||||
|
null,
|
||||||
|
emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction() }
|
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction() }
|
||||||
|
@ -7,16 +7,17 @@ release, see :doc:`upgrade-notes`.
|
|||||||
Unreleased
|
Unreleased
|
||||||
----------
|
----------
|
||||||
|
|
||||||
* Marked the `Attachment` interface as `@DoNotImplement` because it is not meant to be extended by CorDapp developers. If you have already done so,
|
* Marked the ``Attachment`` interface as ``@DoNotImplement`` because it is not meant to be extended by CorDapp developers. If you have already
|
||||||
please get in contact on the usual communication channels.
|
done so, please get in contact on the usual communication channels.
|
||||||
|
|
||||||
* Added auto-acceptance of network parameters for network updates. This behaviour is available for a subset of the network parameters
|
* Added auto-acceptance of network parameters for network updates. This behaviour is available for a subset of the network parameters
|
||||||
and is configurable via the node config. See :doc:`network-map` for more information.
|
and is configurable via the node config. See :doc:`network-map` for more information.
|
||||||
|
|
||||||
* Deprecated `SerializationContext.withAttachmentsClassLoader`. This functionality has always been disabled by flags
|
* Deprecated ``SerializationContext.withAttachmentsClassLoader``. This functionality has always been disabled by flags
|
||||||
and there is no reason for a CorDapp developer to use it. It is just an internal implementation detail of Corda.
|
and there is no reason for a CorDapp developer to use it. It is just an internal implementation detail of Corda.
|
||||||
|
|
||||||
* Deprecated the `LedgerTransaction` constructor. No client code should call it directly. LedgerTransactions can be created from WireTransactions if required.
|
* Deprecated all means to directly create a ``LedgerTransaction`` instance, as client code is only meant to get hold of a ``LedgerTransaction``
|
||||||
|
via ``WireTransaction.toLedgerTransaction``.
|
||||||
|
|
||||||
* Introduced new optional network bootstrapper command line options (--register-package-owner, --unregister-package-owner)
|
* Introduced new optional network bootstrapper command line options (--register-package-owner, --unregister-package-owner)
|
||||||
to register/unregister a java package namespace with an associated owner in the network parameter packageOwnership whitelist.
|
to register/unregister a java package namespace with an associated owner in the network parameter packageOwnership whitelist.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
.. highlight:: kotlin
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||||
|
|
||||||
Default Class Evolution
|
Default Class Evolution
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
import net.corda.core.internal.NamedCacheFactory
|
||||||
|
import net.corda.core.internal.createComponentGroups
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
@ -154,7 +155,7 @@ fun createWireTransaction(inputs: List<StateRef>,
|
|||||||
notary: Party?,
|
notary: Party?,
|
||||||
timeWindow: TimeWindow?,
|
timeWindow: TimeWindow?,
|
||||||
privacySalt: PrivacySalt = PrivacySalt()): WireTransaction {
|
privacySalt: PrivacySalt = PrivacySalt()): WireTransaction {
|
||||||
val componentGroups = WireTransaction.createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow)
|
val componentGroups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList())
|
||||||
return WireTransaction(componentGroups, privacySalt)
|
return WireTransaction(componentGroups, privacySalt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user