Merge pull request #1595 from corda/shams-os-merge-231118

O/S merge until e14421b
This commit is contained in:
Michele Sollecito 2018-11-25 13:22:40 +00:00 committed by GitHub
commit cccdb2882a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 404 additions and 245 deletions

View File

@ -431,7 +431,9 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte
public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash
public void extractFile(String, java.io.OutputStream)
@NotNull
public abstract java.util.List<java.security.PublicKey> getSigners()
public abstract java.util.List<java.security.PublicKey> getSignerKeys()
@NotNull
public abstract java.util.List<net.corda.core.identity.Party> getSigners()
public abstract int getSize()
@NotNull
public abstract java.io.InputStream open()
@ -542,7 +544,7 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang
@NotNull
public net.corda.core.crypto.SecureHash getId()
@NotNull
public java.util.List<java.security.PublicKey> getSigners()
public java.util.List<net.corda.core.identity.Party> getSigners()
public int getSize()
@Nullable
public final String getUploader()

View File

@ -203,6 +203,7 @@ see changes to this list.
* tomtau
* Tudor Malene (R3)
* Tushar Singh Bora (Accenture)
* Vardan Nadkarni (Persistent Systems Limited)
* varunkm
* Venelin Stoykov (INDUSTRIA)
* verymahler

View File

@ -27,6 +27,7 @@ import net.corda.core.crypto.*
import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.createComponentGroups
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.SerializedBytes
@ -187,6 +188,7 @@ private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
value.commands,
value.timeWindow,
value.attachments,
value.references,
value.privacySalt
))
}
@ -195,13 +197,14 @@ private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): WireTransaction {
val wrapper = parser.readValueAs<WireTransactionJson>()
val componentGroups = WireTransaction.createComponentGroups(
val componentGroups = createComponentGroups(
wrapper.inputs,
wrapper.outputs,
wrapper.commands,
wrapper.attachments,
wrapper.notary,
wrapper.timeWindow
wrapper.timeWindow,
wrapper.references
)
return WireTransaction(componentGroups, wrapper.privacySalt)
}
@ -214,6 +217,7 @@ private class WireTransactionJson(val id: SecureHash,
val commands: List<Command<*>>,
val timeWindow: TimeWindow?,
val attachments: List<SecureHash>,
val references: List<StateRef>,
val privacySalt: PrivacySalt)
private interface TransactionStateMixin {

View File

@ -93,7 +93,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
services = rigorousMock()
cordappProvider = rigorousMock()
doReturn(cordappProvider).whenever(services).cordappProvider
doReturn(testNetworkParameters()).whenever(services).networkParameters
doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters
doReturn(attachments).whenever(services).attachments
}
@ -234,7 +234,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
val attachment = rigorousMock<ContractAttachment>()
doReturn(attachment).whenever(attachmentStorage).openAttachment(attachmentId)
doReturn(attachmentId).whenever(attachment).id
doReturn(emptyList<Party>()).whenever(attachment).signers
doReturn(emptyList<Party>()).whenever(attachment).signerKeys
doReturn(setOf(DummyContract.PROGRAM_ID)).whenever(attachment).allContracts
doReturn("app").whenever(attachment).uploader
@ -245,6 +245,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
outputs = mutableListOf(createTransactionState()),
commands = mutableListOf(Command(DummyCommandData, listOf(BOB_PUBKEY))),
window = TimeWindow.fromStartAndDuration(Instant.now(), 1.hours),
references = mutableListOf(StateRef(SecureHash.randomSHA256(), 0)),
privacySalt = net.corda.core.contracts.PrivacySalt()
).toWireTransaction(services)
val stx = sign(wtx)
@ -253,7 +254,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
println(mapper.writeValueAsString(json))
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
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[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
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[5].childrenAs<Command<*>>(mapper)).isEqualTo(wtx.commands)
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<SignedTransaction>(json)).isEqualTo(stx)
}

View File

@ -40,10 +40,12 @@ class AttachmentTest {
@Before
fun setup() {
attachment = object : Attachment {
override val signerKeys: List<PublicKey>
get() = listOf(ALICE_KEY)
override val id: SecureHash
get() = SecureHash.allOnesHash
override val signers: List<PublicKey>
get() = listOf(ALICE_KEY)
override val signers: List<Party>
get() = listOf(ALICE)
override val size: Int
get() = jarData.size

View File

@ -3,13 +3,19 @@ package net.corda.deterministic.verifier
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.PublicKey
@CordaSerializable
class MockContractAttachment(override val id: SecureHash = SecureHash.zeroHash, val contract: ContractClassName, override val signers: List<PublicKey> = emptyList()) : Attachment {
class MockContractAttachment(
override val id: SecureHash = SecureHash.zeroHash,
val contract: ContractClassName,
override val signerKeys: List<PublicKey> = emptyList(),
override val signers: List<Party> = emptyList()
) : Attachment {
override fun open(): InputStream = ByteArrayInputStream(id.bytes)
override val size = id.size
}

View File

@ -1,6 +1,8 @@
package net.corda.core.contracts
import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM
import net.corda.core.identity.Party
import net.corda.core.internal.extractFile
import net.corda.core.serialization.CordaSerializable
import java.io.FileNotFoundException
@ -31,6 +33,7 @@ import java.util.jar.JarInputStream
*/
@KeepForDJVM
@CordaSerializable
@DoNotImplement
interface Attachment : NamedByHash {
fun open(): InputStream
@ -51,11 +54,20 @@ interface Attachment : NamedByHash {
@JvmDefault
fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }
/**
* The parties that have correctly signed the whole attachment.
* Even though this returns a list of party objects, it is not required that these parties exist on the network, but rather they are a mapping from the signing key to the X.500 name.
*
* Note: Anyone can sign attachments, not only Corda parties. It's recommended to use [signerKeys].
*/
@Deprecated("Use signerKeys. There is no requirement that attachment signers are Corda parties.")
val signers: List<Party>
/**
* The keys that have correctly signed the whole attachment.
* Can be empty, for example non-contract attachments won't be necessarily be signed.
*/
val signers: List<PublicKey>
val signerKeys: List<PublicKey>
/**
* Attachment size in bytes.

View File

@ -72,7 +72,7 @@ interface AttachmentConstraint {
// You can transition from the WhitelistConstraint to the SignatureConstraint only if all signers of the JAR are required to sign in the future.
input is WhitelistedByZoneAttachmentConstraint && output is SignatureAttachmentConstraint ->
attachment.signers.isNotEmpty() && output.key.keys.containsAll(attachment.signers)
attachment.signerKeys.isNotEmpty() && output.key.keys.containsAll(attachment.signerKeys)
else -> false
}
@ -180,5 +180,5 @@ data class SignatureAttachmentConstraint(
val key: PublicKey
) : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean =
key.isFulfilledBy(attachment.signers.map { it })
key.isFulfilledBy(attachment.signerKeys.map { it })
}

View File

@ -18,7 +18,7 @@ class ContractAttachment @JvmOverloads constructor(
val contract: ContractClassName,
val additionalContracts: Set<ContractClassName> = emptySet(),
val uploader: String? = null,
override val signers: List<PublicKey> = emptyList()) : Attachment by attachment {
override val signerKeys: List<PublicKey> = emptyList()) : Attachment by attachment {
val allContracts: Set<ContractClassName> get() = additionalContracts + contract

View File

@ -6,6 +6,7 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializeAsTokenContext
import java.io.FileNotFoundException
@ -47,10 +48,15 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
override val size: Int get() = attachmentData.size
override fun open(): InputStream = attachmentData.inputStream()
override val signers: List<PublicKey> by lazy {
override val signerKeys: List<PublicKey> by lazy {
openAsJAR().use(JarSignatureCollector::collectSigners)
}
override val signers: List<Party> by lazy {
openAsJAR().use(JarSignatureCollector::collectSigningParties)
}
override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
override fun hashCode() = id.hashCode()
override fun toString() = "${javaClass.simpleName}(id=$id)"

View File

@ -1,10 +1,6 @@
package net.corda.core.internal
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.CordappConfig
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.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import org.slf4j.MDC
// *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]
?: 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)

View File

@ -1,18 +1,13 @@
package net.corda.core.internal
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.componentHash
import net.corda.core.crypto.sha256
import net.corda.core.identity.Party
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializationContext
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.serialization.*
import net.corda.core.transactions.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.lazyMapped
import java.io.ByteArrayOutputStream
@ -121,4 +116,40 @@ fun deserialiseCommands(componentGroups: List<ComponentGroup>,
}
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)
}

View File

@ -252,7 +252,7 @@ data class ContractUpgradeLedgerTransaction(
private fun verifyConstraints() {
val attachmentForConstraintVerification = AttachmentWithContext(
legacyContractAttachment as? ContractAttachment
?: ContractAttachment(legacyContractAttachment, legacyContractClassName, signers = legacyContractAttachment.signers),
?: ContractAttachment(legacyContractAttachment, legacyContractClassName, signerKeys = legacyContractAttachment.signerKeys),
upgradedContract.legacyContract,
networkParameters.whitelistedContractImplementations
)

View File

@ -8,10 +8,11 @@ import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.ConstructorForDeserialization
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.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.warnOnce
import java.util.*
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.
*/
// 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
@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. */
override val inputs: List<StateAndRef<ContractState>>,
override val outputs: List<TransactionState<ContractState>>,
@ -46,39 +48,15 @@ data class LedgerTransaction private constructor(
override val notary: Party?,
val timeWindow: TimeWindow?,
val privacySalt: PrivacySalt,
private val networkParameters: NetworkParameters?,
override val references: List<StateAndRef<ContractState>>,
val componentGroups: List<ComponentGroup>?,
val resolvedInputBytes: List<SerializedStateAndRef>?,
val resolvedReferenceBytes: List<SerializedStateAndRef>?
val networkParameters: NetworkParameters?,
override val references: List<StateAndRef<ContractState>>
//DOCEND 1
) : 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 {
checkBaseInvariants()
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
@ -87,10 +65,10 @@ data class LedgerTransaction private constructor(
}
companion object {
private val logger = loggerFor<LedgerTransaction>()
private val logger = contextLogger()
@CordaInternal
internal fun makeLedgerTransaction(
internal fun create(
inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandWithParties<CommandData>>,
@ -101,10 +79,16 @@ data class LedgerTransaction private constructor(
privacySalt: PrivacySalt,
networkParameters: NetworkParameters?,
references: List<StateAndRef<ContractState>>,
componentGroups: List<ComponentGroup>,
resolvedInputBytes: List<SerializedStateAndRef>,
resolvedReferenceBytes: List<SerializedStateAndRef>
) = LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, resolvedInputBytes, resolvedReferenceBytes)
componentGroups: List<ComponentGroup>? = null,
serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null
): 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 }
@ -137,7 +121,7 @@ data class LedgerTransaction private constructor(
AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader ->
val internalTx = createInternalLedgerTransaction()
val internalTx = createLtxForVerification()
// TODO - verify for version downgrade
validatePackageOwnership(contractAttachmentsByContract)
@ -166,7 +150,7 @@ data class LedgerTransaction private constructor(
contractsAndOwners.forEach { contract, owner ->
val attachment = contractAttachmentsByContract[contract]!!
if (!owner.isFulfilledBy(attachment.signers)) {
if (!owner.isFulfilledBy(attachment.signerKeys)) {
throw TransactionVerificationException.ContractAttachmentNotSignedByPackageOwnerException(this.id, id, contract)
}
}
@ -195,7 +179,9 @@ data class LedgerTransaction private constructor(
* * Constraints should be one of the valid supported ones.
* * 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.
for (state in internalTx.allStates) {
checkConstraintValidity(state)
@ -277,30 +263,38 @@ data class LedgerTransaction private constructor(
throw TransactionVerificationException.ContractCreationError(id, className, e)
}
private fun createInternalLedgerTransaction(): LedgerTransaction {
return if (resolvedInputBytes != null && resolvedReferenceBytes != null && componentGroups != null) {
private fun createLtxForVerification(): LedgerTransaction {
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.
val resolvedDeserializedInputs = resolvedInputBytes.map { StateAndRef(it.serializedState.deserialize(), it.ref) }
val resolvedDeserializedReferences = resolvedReferenceBytes.map { StateAndRef(it.serializedState.deserialize(), it.ref) }
val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
val deserializedReferences = serializedReferences.map { it.toStateAndRef() }
val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
val deserializedCommands = deserialiseCommands(this.componentGroups, forceDeserialize = true)
val authenticatedArgs = deserializedCommands.map { cmd ->
val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true)
val authenticatedDeserializedCommands = deserializedCommands.map { cmd ->
val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties
CommandWithParties(cmd.signers, parties, cmd.value)
}
val ledgerTransactionToVerify = this.copy(
inputs = resolvedDeserializedInputs,
LedgerTransaction(
inputs = deserializedInputs,
outputs = deserializedOutputs,
commands = authenticatedArgs,
references = resolvedDeserializedReferences)
ledgerTransactionToVerify
commands = authenticatedDeserializedCommands,
attachments = this.attachments,
id = this.id,
notary = this.notary,
timeWindow = this.timeWindow,
privacySalt = this.privacySalt,
networkParameters = this.networkParameters,
references = deserializedReferences
)
} else {
// 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. The result of the verify method might not be accurate.")
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.")
this
}
}
@ -766,7 +760,92 @@ data class LedgerTransaction private constructor(
*/
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,
outputs: List<TransactionState<ContractState>> = this.outputs,
commands: List<CommandWithParties<CommandData>> = this.commands,
@ -776,16 +855,18 @@ data class LedgerTransaction private constructor(
timeWindow: TimeWindow? = this.timeWindow,
privacySalt: PrivacySalt = this.privacySalt,
networkParameters: NetworkParameters? = this.networkParameters
) = copy(inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references
)
): LedgerTransaction {
return LedgerTransaction(
inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references
)
}
}

View File

@ -11,11 +11,7 @@ import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.keys
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext
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.internal.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
@ -24,7 +20,6 @@ import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.warnOnce
import java.security.PublicKey
@ -165,14 +160,15 @@ open class TransactionBuilder @JvmOverloads constructor(
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
WireTransaction(
WireTransaction.createComponentGroups(
createComponentGroups(
inputStates(),
resolvedOutputs,
commands,
(allContractAttachments + attachments).toSortedSet().toList(), // Sort the attachments to ensure transaction builds are stable.
notary,
window,
referenceStates),
referenceStates
),
privacySalt
)
}
@ -347,7 +343,7 @@ open class TransactionBuilder @JvmOverloads constructor(
attachmentToUse: ContractAttachment,
services: ServicesForResolution): AttachmentConstraint = when {
inputStates != null -> attachmentConstraintsTransition(inputStates.groupBy { it.constraint }.keys, attachmentToUse)
attachmentToUse.signers.isNotEmpty() && services.networkParameters.minimumPlatformVersion < 4 -> {
attachmentToUse.signerKeys.isNotEmpty() && services.networkParameters.minimumPlatformVersion < 4 -> {
log.warnOnce("Signature constraints not available on network requiring a minimum platform version of 4. Current is: ${services.networkParameters.minimumPlatformVersion}.")
if (useWhitelistedByZoneAttachmentConstraint(contractClassName, services.networkParameters)) {
log.warnOnce("Reverting back to using whitelisted zone constraints for contract $contractClassName")
@ -357,7 +353,7 @@ open class TransactionBuilder @JvmOverloads constructor(
HashAttachmentConstraint(attachmentToUse.id)
}
}
attachmentToUse.signers.isNotEmpty() -> makeSignatureAttachmentConstraint(attachmentToUse.signers)
attachmentToUse.signerKeys.isNotEmpty() -> makeSignatureAttachmentConstraint(attachmentToUse.signerKeys)
useWhitelistedByZoneAttachmentConstraint(contractClassName, services.networkParameters) -> WhitelistedByZoneAttachmentConstraint
else -> HashAttachmentConstraint(attachmentToUse.id)
}
@ -398,8 +394,8 @@ open class TransactionBuilder @JvmOverloads constructor(
constraints.any { it is SignatureAttachmentConstraint } && constraints.any { it is WhitelistedByZoneAttachmentConstraint } -> {
val signatureConstraint = constraints.mapNotNull { it as? SignatureAttachmentConstraint }.single()
when {
attachmentToUse.signers.isEmpty() -> throw IllegalArgumentException("Cannot mix a state with the WhitelistedByZoneAttachmentConstraint and a state with the SignatureAttachmentConstraint, when the latest attachment is not signed. Please contact your Zone operator.")
signatureConstraint.key.keys.containsAll(attachmentToUse.signers) -> signatureConstraint
attachmentToUse.signerKeys.isEmpty() -> throw IllegalArgumentException("Cannot mix a state with the WhitelistedByZoneAttachmentConstraint and a state with the SignatureAttachmentConstraint, when the latest attachment is not signed. Please contact your Zone operator.")
signatureConstraint.key.keys.containsAll(attachmentToUse.signerKeys) -> signatureConstraint
else -> throw IllegalArgumentException("Attempting to transition a WhitelistedByZoneAttachmentConstraint state backed by an attachment signed by multiple parties to a weaker SignatureConstraint that does not require all those signatures. Please contact your Zone operator.")
}
}

View File

@ -4,18 +4,19 @@ import net.corda.core.CordaInternal
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
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.identity.Party
import net.corda.core.internal.SerializedStateAndRef
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.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.lazyMapped
@ -53,7 +54,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
@DeleteForDJVM
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
@JvmOverloads
constructor(
@ -64,7 +66,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt = PrivacySalt()
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow), privacySalt)
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList()), privacySalt)
init {
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(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRefComponent = { resolveStateRefBinaryComponent(it, services) },
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
networkParameters = services.networkParameters
)
}
@ -130,38 +132,44 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRefComponent: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
networkParameters: NetworkParameters?
): LedgerTransaction {
// 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) }
CommandWithParties(cmd.signers, parties, cmd.value)
}
val resolvedInputBytes = inputs.map { ref ->
SerializedStateAndRef(resolveStateRefComponent(ref)
?: throw TransactionResolutionException(ref.txhash), ref)
}
val resolvedInputs = resolvedInputBytes.lazyMapped { (serialized, ref), _ ->
StateAndRef(serialized.deserialize(), ref)
val serializedResolvedInputs = inputs.map { ref ->
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
}
val resolvedInputs = serializedResolvedInputs.lazyMapped { star, _ -> star.toStateAndRef() }
val resolvedReferenceBytes = references.map { ref ->
SerializedStateAndRef(resolveStateRefComponent(ref)
?: throw TransactionResolutionException(ref.txhash), ref)
}
val resolvedReferences = resolvedReferenceBytes.lazyMapped { (serialized, ref), _ ->
StateAndRef(serialized.deserialize(), ref)
val serializedResolvedReferences = references.map { ref ->
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
}
val resolvedReferences = serializedResolvedReferences.lazyMapped { star, _ -> star.toStateAndRef() }
val attachments = attachments.lazyMapped { att, _ ->
resolveAttachment(att) ?: throw AttachmentResolutionException(att)
}
val resolvedAttachments = attachments.lazyMapped { 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
}
@ -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.
* 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
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.
ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) }
minus(ltx.resolvedInputBytes!!.sumBy { it.serializedState.size })
minus(ltx.resolvedReferenceBytes!!.sumBy { it.serializedState.size })
minus(resolvedSerializedInputs.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.
minus(componentGroupSize(COMMANDS_GROUP))
@ -277,33 +288,15 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
companion object {
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
@Deprecated("Do not use, this is internal API")
fun createComponentGroups(inputs: List<StateRef>,
outputs: List<TransactionState<ContractState>>,
commands: List<Command<*>>,
attachments: List<SecureHash>,
notary: Party?,
timeWindow: TimeWindow?,
references: List<StateRef> = emptyList()): List<ComponentGroup> {
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
timeWindow: TimeWindow?): List<ComponentGroup> {
return createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList())
}
/**
@ -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.
*/
@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) {
val coreTransaction = services.validatedTransactions.getTransaction(stateRef.txhash)?.coreTransaction
?: throw TransactionResolutionException(stateRef.txhash)
@ -333,11 +326,26 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
override fun toString(): String {
val buf = StringBuilder()
buf.appendln("Transaction:")
for (reference in references) buf.appendln("${Emoji.rightArrow}REFS: $reference")
for (input in inputs) buf.appendln("${Emoji.rightArrow}INPUT: $input")
for ((data) in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: $data")
for (command in commands) buf.appendln("${Emoji.diamond}COMMAND: $command")
for (attachment in attachments) buf.appendln("${Emoji.paperclip}ATTACHMENT: $attachment")
for (reference in references) {
val emoji = Emoji.rightArrow
buf.appendln("${emoji}REFS: $reference")
}
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()
}

View File

@ -7,7 +7,6 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.MissingContractAttachments
import net.corda.finance.POUNDS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
@ -223,7 +222,7 @@ class ConstraintsPropagationTests {
fun `Attachment canBeTransitionedFrom behaves as expected`() {
val attachment = mock<ContractAttachment>()
whenever(attachment.signers).thenReturn(listOf(ALICE_PARTY.owningKey))
whenever(attachment.signerKeys).thenReturn(listOf(ALICE_PARTY.owningKey))
// Exhaustive positive check
assertTrue(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment))

View File

@ -3,6 +3,7 @@ package net.corda.core.contracts
import com.nhaarman.mockito_kotlin.doAnswer
import com.nhaarman.mockito_kotlin.spy
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.identity.Party
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.IOException
@ -30,7 +31,8 @@ class AttachmentTest {
val attachment = object : Attachment {
override val id get() = throw UnsupportedOperationException()
override fun open() = inputStream
override val signers get() = throw UnsupportedOperationException()
override val signerKeys get() = throw UnsupportedOperationException()
override val signers: List<Party> get() = throw UnsupportedOperationException()
override val size: Int = 512
}
try {

View File

@ -111,7 +111,8 @@ class AttachmentSerializationTest {
private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment {
override fun open() = throw UnsupportedOperationException("Not implemented.")
override val signers get() = throw UnsupportedOperationException()
override val signerKeys get() = throw UnsupportedOperationException()
override val signers: List<Party> get() = throw UnsupportedOperationException()
override val size get() = throw UnsupportedOperationException()
}

View File

@ -3,6 +3,7 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.crypto.*
import net.corda.core.internal.createComponentGroups
import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import net.corda.testing.contracts.DummyContract
@ -124,7 +125,7 @@ class CompatibleTransactionTests {
@Test
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)
assertEquals(wireTransactionA, wireTransactionOldConstructor)

View File

@ -50,7 +50,7 @@ class TransactionBuilderTest {
doReturn(contractAttachmentId).whenever(attachment).id
doReturn(setOf(DummyContract.PROGRAM_ID)).whenever(attachment).allContracts
doReturn("app").whenever(attachment).uploader
doReturn(emptyList<Party>()).whenever(attachment).signers
doReturn(emptyList<Party>()).whenever(attachment).signerKeys
}
@Test
@ -128,12 +128,12 @@ class TransactionBuilderTest {
private val unsignedAttachment = ContractAttachment(object : AbstractAttachment({ byteArrayOf() }) {
override val id: SecureHash get() = throw UnsupportedOperationException()
override val signers: List<PublicKey> get() = emptyList()
override val signerKeys: List<PublicKey> get() = emptyList()
}, DummyContract.PROGRAM_ID)
private fun signedAttachment(vararg parties: Party) = ContractAttachment(object : AbstractAttachment({ byteArrayOf() }) {
override val id: SecureHash get() = throw UnsupportedOperationException()
override val signers: List<PublicKey> get() = parties.map { it.owningKey }
}, DummyContract.PROGRAM_ID, signers = parties.map { it.owningKey })
override val signerKeys: List<PublicKey> get() = parties.map { it.owningKey }
}, DummyContract.PROGRAM_ID, signerKeys = parties.map { it.owningKey })
}

View File

@ -124,7 +124,7 @@ class TransactionTests {
val id = SecureHash.randomSHA256()
val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt()
val transaction = LedgerTransaction(
val transaction = LedgerTransaction.create(
inputs,
outputs,
commands,
@ -133,7 +133,8 @@ class TransactionTests {
null,
timeWindow,
privacySalt,
testNetworkParameters()
testNetworkParameters(),
emptyList()
)
transaction.verify()
@ -166,7 +167,7 @@ class TransactionTests {
val id = SecureHash.randomSHA256()
val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt()
fun buildTransaction() = LedgerTransaction(
fun buildTransaction() = LedgerTransaction.create(
inputs,
outputs,
commands,
@ -174,7 +175,9 @@ class TransactionTests {
id,
notary,
timeWindow,
privacySalt
privacySalt,
null,
emptyList()
)
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction() }

View File

@ -7,13 +7,17 @@ release, see :doc:`upgrade-notes`.
Unreleased
----------
* Marked the ``Attachment`` interface as ``@DoNotImplement`` because it is not meant to be extended by CorDapp developers. If you have already
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
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
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 ``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.
* 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)
to register/unregister a java package namespace with an associated owner in the network parameter packageOwnership whitelist.

View File

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

View File

@ -1,44 +1,38 @@
Testing Corda
=============
Testing your changes
====================
Automated Tests
Automated tests
---------------
Corda has a suite of tests that any contributing developers must maintain and extend when adding new code.
Corda has a maintained suite of tests that any contributing developers must maintain and add to if new code has been added.
There are several test suites:
There are several distinct test suites each with a different purpose;
* **Unit tests**: These are traditional unit tests that should only test a single code unit, typically a method or class.
* **Integration tests**: These tests should test the integration of small numbers of units, preferably with mocked out services.
* **Smoke tests**: These are full end to end tests which start a full set of Corda nodes and verify broader behaviour.
* **Other**: These include tests such as performance tests, stress tests, etc, and may be in an external repo.
**Unit tests**: These are traditional unit tests that should only test a single code unit, typically a method or class.
Running the automated tests
^^^^^^^^^^^^^^^^^^^^^^^^^^^
These tests are mostly written with JUnit and can be run via ``gradle``:
**Integration tests**: These tests should test the integration of small numbers of units, preferably with mocked out services.
**Smoke tests**: These are full end to end tests which start a full set of Corda nodes and verify broader behaviour.
**Other**: These include tests such as performance tests, stress tests, etc, and may be in an external repo.
These tests are mostly written with JUnit and can be run via ``gradle``. On windows run ``gradlew test integrationTest
smokeTest`` and on unix run ``./gradlw test integrationTest smokeTest`` or any combination of these three arguments.
* **Windows**: Run ``gradlew test integrationTest smokeTest``
* **Unix/Mac OSX**: Run ``./gradlew test integrationTest smokeTest``
Before creating a pull request please make sure these pass.
Manual Testing
Manual testing
--------------
You should manually test anything that would be impacted by your changes. The areas that usually need to be manually tested and when are
as follows:
Manual testing would ideally be done for every set of changes merged into master, but practically you should manually test
anything that would be impacted by your changes. The areas that usually need to be manually tested and when are below;
* **Node startup** - changes in the ``node`` or ``node:capsule`` project in both the Kotlin or gradle or the ``cordformation`` gradle plugin.
* **Sample project** - changes in the ``samples`` project. eg; changing the IRS demo means you should manually test the IRS demo.
* **Explorer** - changes to the ``tools/explorer`` project.
* **Demobench** - changes to the ``tools/demobench`` project.
**Node startup** - changes in the ``node`` or ``node:capsule`` project in both the Kotlin or gradle or the ``cordformation`` gradle plugin.
**Sample project** - changes in the ``samples`` project. eg; changing the IRS demo means you should manually test the IRS demo.
**Explorer** - changes to the ``tools/explorer`` project.
**Demobench** - changes to the ``tools/demobench`` project.
How to manually test each of these areas differs and is currently not fully specified. For now the best thing to do is
ensure the program starts, that you can interact with it, and that no exceptions are generated in normal operation.
TODO: Add instructions on manual testing
How to manually test each of these areas differs and is currently not fully specified. For now the best thing to do is to ensure the
program starts, that you can interact with it, and that no exceptions are generated in normal operation.
External Database Testing
-------------------------

View File

@ -87,7 +87,7 @@ class AttachmentsClassLoaderStaticContractTests {
doReturn(it.cordappProvider.getContractAttachmentID(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)).whenever(attachment).id
doReturn(setOf(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)).whenever(attachment).allContracts
doReturn("app").whenever(attachment).uploader
doReturn(emptyList<Party>()).whenever(attachment).signers
doReturn(emptyList<Party>()).whenever(attachment).signerKeys
}
@Test

View File

@ -211,7 +211,7 @@ object DefaultKryoCustomizer {
output.writeString(obj.contract)
kryo.writeClassAndObject(output, obj.additionalContracts)
output.writeString(obj.uploader)
kryo.writeClassAndObject(output, obj.signers)
kryo.writeClassAndObject(output, obj.signerKeys)
}
@Suppress("UNCHECKED_CAST")

View File

@ -11,13 +11,10 @@ import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
import net.corda.node.services.keys.cryptoservice.utimaco.UtimacoCryptoService
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.node.services.keys.cryptoservice.utimaco.UtimacoCryptoService
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.nodeapi.internal.config.*
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.tools.shell.SSHDConfiguration

View File

@ -45,7 +45,8 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
PersistentIdentityService.PersistentIdentityNames::class.java,
ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
RunOnceService.MutualExclusion::class.java,
PersistentKeyManagementService.PublicKeyHashToExternalId::class.java)) {
PersistentKeyManagementService.PublicKeyHashToExternalId::class.java
)) {
override val migrationResource = "node-core.changelog-master"
}

View File

@ -12,5 +12,4 @@
<include file="migration/vault-schema.changelog-pkey-swap.xml"/>
<include file="migration/vault-schema.changelog-v7.xml"/>
<include file="migration/vault-schema.changelog-v8.xml"/>
</databaseChangeLog>

View File

@ -278,7 +278,7 @@ class NodeConfigurationImplTest {
fun `jmxReporterType is null and defaults to Jokolia`() {
val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
assertEquals(JmxReporterType.JOLOKIA.toString(), nodeConfig.jmxReporterType.toString())
}
@Test
@ -286,7 +286,7 @@ class NodeConfigurationImplTest {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC"))
val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
assertTrue(JmxReporterType.NEW_RELIC.toString() == nodeConfig.jmxReporterType.toString())
assertEquals(JmxReporterType.NEW_RELIC.toString(), nodeConfig.jmxReporterType.toString())
}
@Test
@ -294,7 +294,7 @@ class NodeConfigurationImplTest {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA"))
val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
assertEquals(JmxReporterType.JOLOKIA.toString(), nodeConfig.jmxReporterType.toString())
}
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfigurationImpl {

View File

@ -91,7 +91,7 @@ class NodeAttachmentServiceTest {
val signedJar = jarAndSigner.first
signedJar.inputStream().use { jarStream ->
val attachmentId = storage.importAttachment(jarStream, "test", null)
assertEquals(listOf(jarAndSigner.second.hash), storage.openAttachment(attachmentId)!!.signers.map { it.hash })
assertEquals(listOf(jarAndSigner.second.hash), storage.openAttachment(attachmentId)!!.signerKeys.map { it.hash })
}
}
}
@ -102,7 +102,7 @@ class NodeAttachmentServiceTest {
val jarName = makeTestContractJar(it.path, "com.example.MyContract")
it.path.resolve(jarName).inputStream().use { jarStream ->
val attachmentId = storage.importAttachment(jarStream, "test", null)
assertEquals(0, storage.openAttachment(attachmentId)!!.signers.size)
assertEquals(0, storage.openAttachment(attachmentId)!!.signerKeys.size)
}
}
}

View File

@ -24,7 +24,7 @@ class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerialize
} catch (e: Exception) {
throw MissingAttachmentsException(listOf(obj.id))
}
return ContractAttachmentProxy(GeneratedAttachment(bytes), obj.contract, obj.additionalContracts, obj.uploader, obj.signers)
return ContractAttachmentProxy(GeneratedAttachment(bytes), obj.contract, obj.additionalContracts, obj.uploader, obj.signerKeys)
}
override fun fromProxy(proxy: ContractAttachmentProxy): ContractAttachment {

View File

@ -10,6 +10,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.createComponentGroups
import net.corda.core.node.NodeInfo
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.loggerFor
@ -154,7 +155,7 @@ fun createWireTransaction(inputs: List<StateRef>,
notary: Party?,
timeWindow: TimeWindow?,
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)
}

View File

@ -61,7 +61,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
fun getAttachmentIdAndBytes(jar: InputStream): Pair<AttachmentId, ByteArray> = jar.readFully().let { bytes -> Pair(bytes.sha256(), bytes) }
private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash, override val signers: List<PublicKey>) : AbstractAttachment(dataLoader)
private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash, override val signerKeys: List<PublicKey>) : AbstractAttachment(dataLoader)
private fun importAttachmentInternal(jar: InputStream, uploader: String, contractClassNames: List<ContractClassName>? = null, attachmentId: AttachmentId? = null, signers: List<PublicKey> = emptyList()): AttachmentId {
// JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here.

View File

@ -165,6 +165,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
nodeInfoFilesCopier.reset()
notaryIdentity = null
networkParametersCopier = null
SuggestedDetails.reset()
}
/**

View File

@ -27,7 +27,20 @@ object SuggestedDetails {
private var cursor = 0
val nextBank: Pair<String, String> get() = banks[cursor++ % banks.size]
fun nextBank(exists: (String) -> Boolean): Pair<String, String> {
for (i in 0 until banks.size) {
val bank = banks[cursor]
if (!exists(bank.first)) {
return bank
}
cursor = (cursor + 1) % banks.size
}
return banks[cursor]
}
fun reset() {
cursor = 0
}
}
class NodeData {

View File

@ -206,7 +206,8 @@ class NodeTabView : Fragment() {
model.webPort.value = nodeController.nextPort
model.h2Port.value = nodeController.nextPort
val defaults = SuggestedDetails.nextBank
val defaults = SuggestedDetails.nextBank(nodeController::nameExists)
model.legalName.value = defaults.first
model.nearestCity.value = CityDatabase[defaults.second]