Merge remote-tracking branch 'origin/release/os/4.4' into adel/merge-from-4.4-19-Feb-21

This commit is contained in:
Adel El-Beik 2021-02-19 13:55:04 +00:00
commit 9cd02dc62d
18 changed files with 130 additions and 45 deletions

View File

@ -6273,7 +6273,7 @@ public abstract class net.corda.core.transactions.FullTransaction extends net.co
public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction
public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt)
public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters) public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters)
public <init>(java.util.List, java.util.List, java.util.List, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters, java.util.List, java.util.List, java.util.List, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function2, kotlin.jvm.internal.DefaultConstructorMarker) public <init>(java.util.List, java.util.List, java.util.List, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters, java.util.List, java.util.List, java.util.List, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function2, net.corda.core.serialization.internal.AttachmentsClassLoaderCache, kotlin.jvm.internal.DefaultConstructorMarker)
@NotNull @NotNull
public final java.util.List<net.corda.core.contracts.Command<T>> commandsOfType(Class<T>) public final java.util.List<net.corda.core.contracts.Command<T>> commandsOfType(Class<T>)
@NotNull @NotNull

View File

@ -53,7 +53,7 @@ class ReceiveFinalityFlowTest {
val paymentReceiverId = paymentReceiverFuture.getOrThrow() val paymentReceiverId = paymentReceiverFuture.getOrThrow()
assertThat(bob.services.vaultService.queryBy<FungibleAsset<*>>().states).isEmpty() assertThat(bob.services.vaultService.queryBy<FungibleAsset<*>>().states).isEmpty()
bob.assertFlowSentForObservationDueToConstraintError(paymentReceiverId) bob.assertFlowSentForObservationDueToUntrustedAttachmentsException(paymentReceiverId)
// Restart Bob with the contracts CorDapp so that it can recover from the error // Restart Bob with the contracts CorDapp so that it can recover from the error
bob = mockNet.restartNode(bob, parameters = InternalMockNodeParameters(additionalCordapps = listOf(FINANCE_CONTRACTS_CORDAPP))) bob = mockNet.restartNode(bob, parameters = InternalMockNodeParameters(additionalCordapps = listOf(FINANCE_CONTRACTS_CORDAPP)))
@ -69,7 +69,7 @@ class ReceiveFinalityFlowTest {
.ofType(R::class.java) .ofType(R::class.java)
} }
private fun TestStartedNode.assertFlowSentForObservationDueToConstraintError(runId: StateMachineRunId) { private fun TestStartedNode.assertFlowSentForObservationDueToUntrustedAttachmentsException(runId: StateMachineRunId) {
val observation = medicalRecordsOfType<Flow>() val observation = medicalRecordsOfType<Flow>()
.filter { it.flowId == runId } .filter { it.flowId == runId }
.toBlocking() .toBlocking()
@ -77,6 +77,6 @@ class ReceiveFinalityFlowTest {
assertThat(observation.outcome).isEqualTo(Outcome.OVERNIGHT_OBSERVATION) assertThat(observation.outcome).isEqualTo(Outcome.OVERNIGHT_OBSERVATION)
assertThat(observation.by).contains(FinalityDoctor) assertThat(observation.by).contains(FinalityDoctor)
val error = observation.errors.single() val error = observation.errors.single()
assertThat(error).isInstanceOf(TransactionVerificationException.ContractConstraintRejection::class.java) assertThat(error).isInstanceOf(TransactionVerificationException.UntrustedAttachmentsException::class.java)
} }
} }

View File

@ -55,7 +55,7 @@ class AttachmentsClassLoaderSerializationTests {
arrayOf(isolatedId, att1, att2).map { storage.openAttachment(it)!! }, arrayOf(isolatedId, att1, att2).map { storage.openAttachment(it)!! },
testNetworkParameters(), testNetworkParameters(),
SecureHash.zeroHash, SecureHash.zeroHash,
{ attachmentTrustCalculator.calculate(it) }) { classLoader -> { attachmentTrustCalculator.calculate(it) }, attachmentsClassLoaderCache = null) { classLoader ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader) val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader)
val contract = contractClass.getDeclaredConstructor().newInstance() as Contract val contract = contractClass.getDeclaredConstructor().newInstance() as Contract
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value) assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)

View File

@ -23,6 +23,7 @@ import net.corda.core.internal.inputStream
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.internal.AttachmentsClassLoader import net.corda.core.serialization.internal.AttachmentsClassLoader
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
@ -521,6 +522,7 @@ class AttachmentsClassLoaderTests {
val id = SecureHash.randomSHA256() val id = SecureHash.randomSHA256()
val timeWindow: TimeWindow? = null val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt() val privacySalt = PrivacySalt()
val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory)
val transaction = createLedgerTransaction( val transaction = createLedgerTransaction(
inputs, inputs,
outputs, outputs,
@ -532,7 +534,8 @@ class AttachmentsClassLoaderTests {
privacySalt, privacySalt,
testNetworkParameters(), testNetworkParameters(),
emptyList(), emptyList(),
isAttachmentTrusted = { true } isAttachmentTrusted = { true },
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
transaction.verify() transaction.verify()
} }

View File

@ -10,6 +10,7 @@ import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.TESTDSL_UPLOADER import net.corda.core.internal.TESTDSL_UPLOADER
import net.corda.core.internal.createLedgerTransaction import net.corda.core.internal.createLedgerTransaction
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
@ -18,6 +19,7 @@ import net.corda.testing.core.*
import net.corda.testing.internal.createWireTransaction import net.corda.testing.internal.createWireTransaction
import net.corda.testing.internal.fakeAttachment import net.corda.testing.internal.fakeAttachment
import net.corda.coretesting.internal.rigorousMock import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.internal.TestingNamedCacheFactory
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.math.BigInteger import java.math.BigInteger
@ -131,6 +133,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 attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
val transaction = createLedgerTransaction( val transaction = createLedgerTransaction(
inputs, inputs,
outputs, outputs,
@ -142,7 +145,8 @@ class TransactionTests {
privacySalt, privacySalt,
testNetworkParameters(), testNetworkParameters(),
emptyList(), emptyList(),
isAttachmentTrusted = { true } isAttachmentTrusted = { true },
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
transaction.verify() transaction.verify()
@ -183,6 +187,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 attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
fun buildTransaction() = createLedgerTransaction( fun buildTransaction() = createLedgerTransaction(
inputs, inputs,
@ -195,7 +200,8 @@ class TransactionTests {
privacySalt, privacySalt,
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))), testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))),
emptyList(), emptyList(),
isAttachmentTrusted = { true } isAttachmentTrusted = { true },
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction().verify() } assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction().verify() }

View File

@ -5,6 +5,7 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.notary.NotaryService
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
// TODO: This should really be called ServiceHubInternal but that name is already taken by net.corda.node.services.api.ServiceHubInternal. // TODO: This should really be called ServiceHubInternal but that name is already taken by net.corda.node.services.api.ServiceHubInternal.
@ -21,6 +22,8 @@ interface ServiceHubCoreInternal : ServiceHub {
val notaryService: NotaryService? val notaryService: NotaryService?
fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver
val attachmentsClassLoaderCache: AttachmentsClassLoaderCache
} }
interface TransactionsResolver { interface TransactionsResolver {

View File

@ -1,5 +1,8 @@
package net.corda.core.serialization.internal package net.corda.core.serialization.internal
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.TransactionVerificationException import net.corda.core.contracts.TransactionVerificationException
@ -21,6 +24,7 @@ import java.lang.ref.WeakReference
import java.net.* import java.net.*
import java.security.Permission import java.security.Permission
import java.util.* import java.util.*
import java.util.function.Function
/** /**
* A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only * A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only
@ -288,31 +292,27 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
*/ */
@VisibleForTesting @VisibleForTesting
object AttachmentsClassLoaderBuilder { object AttachmentsClassLoaderBuilder {
private const val CACHE_SIZE = 1000 const val CACHE_SIZE = 16
// We use a set here because the ordering of attachments doesn't affect code execution, due to the no private val fallBackCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderSimpleCacheImpl(CACHE_SIZE)
// overlap rule, and attachments don't have any particular ordering enforced by the builders. So we
// can just do unordered comparisons here. But the same attachments run with different network parameters
// may behave differently, so that has to be a part of the cache key.
private data class Key(val hashes: Set<SecureHash>, val params: NetworkParameters)
// This runs in the DJVM so it can't use caffeine.
private val cache: MutableMap<Key, SerializationContext> = createSimpleCache<Key, SerializationContext>(CACHE_SIZE).toSynchronised()
/** /**
* Runs the given block with serialization execution context set up with a (possibly cached) attachments classloader. * Runs the given block with serialization execution context set up with a (possibly cached) attachments classloader.
* *
* @param txId The transaction ID that triggered this request; it's unused except for error messages and exceptions that can occur during setup. * @param txId The transaction ID that triggered this request; it's unused except for error messages and exceptions that can occur during setup.
*/ */
@Suppress("LongParameterList")
fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>, fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>,
params: NetworkParameters, params: NetworkParameters,
txId: SecureHash, txId: SecureHash,
isAttachmentTrusted: (Attachment) -> Boolean, isAttachmentTrusted: (Attachment) -> Boolean,
parent: ClassLoader = ClassLoader.getSystemClassLoader(), parent: ClassLoader = ClassLoader.getSystemClassLoader(),
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
block: (ClassLoader) -> T): T { block: (ClassLoader) -> T): T {
val attachmentIds = attachments.map(Attachment::id).toSet() val attachmentIds = attachments.map(Attachment::id).toSet()
val serializationContext = cache.computeIfAbsent(Key(attachmentIds, params)) { val cache = attachmentsClassLoaderCache ?: fallBackCache
val serializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params), Function {
// Create classloader and load serializers, whitelisted classes // Create classloader and load serializers, whitelisted classes
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent) val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent)
val serializers = try { val serializers = try {
@ -335,7 +335,7 @@ object AttachmentsClassLoaderBuilder {
.withWhitelist(whitelistedClasses) .withWhitelist(whitelistedClasses)
.withCustomSerializers(serializers) .withCustomSerializers(serializers)
.withoutCarpenter() .withoutCarpenter()
} })
// Deserialize all relevant classes in the transaction classloader. // Deserialize all relevant classes in the transaction classloader.
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) { return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
@ -419,6 +419,36 @@ private class AttachmentsHolderImpl : AttachmentsHolder {
} }
} }
interface AttachmentsClassLoaderCache {
fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: Function<in AttachmentsClassLoaderKey, out SerializationContext>): SerializationContext
}
@DeleteForDJVM
class AttachmentsClassLoaderCacheImpl(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), AttachmentsClassLoaderCache {
private val cache: Cache<AttachmentsClassLoaderKey, SerializationContext> = cacheFactory.buildNamed(Caffeine.newBuilder(), "AttachmentsClassLoader_cache")
override fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: Function<in AttachmentsClassLoaderKey, out SerializationContext>): SerializationContext {
return cache.get(key, mappingFunction) ?: throw NullPointerException("null returned from cache mapping function")
}
}
class AttachmentsClassLoaderSimpleCacheImpl(cacheSize: Int) : AttachmentsClassLoaderCache {
private val cache: MutableMap<AttachmentsClassLoaderKey, SerializationContext>
= createSimpleCache<AttachmentsClassLoaderKey, SerializationContext>(cacheSize).toSynchronised()
override fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: Function<in AttachmentsClassLoaderKey, out SerializationContext>): SerializationContext {
return cache.computeIfAbsent(key, mappingFunction)
}
}
// We use a set here because the ordering of attachments doesn't affect code execution, due to the no
// overlap rule, and attachments don't have any particular ordering enforced by the builders. So we
// can just do unordered comparisons here. But the same attachments run with different network parameters
// may behave differently, so that has to be a part of the cache key.
data class AttachmentsClassLoaderKey(val hashes: Set<SecureHash>, val params: NetworkParameters)
private class AttachmentURLConnection(url: URL, private val attachment: Attachment) : URLConnection(url) { private class AttachmentURLConnection(url: URL, private val attachment: Attachment) : URLConnection(url) {
override fun getContentLengthLong(): Long = attachment.size.toLong() override fun getContentLengthLong(): Long = attachment.size.toLong()
override fun getInputStream(): InputStream = attachment.open() override fun getInputStream(): InputStream = attachment.open()
@ -435,3 +465,5 @@ private class AttachmentURLConnection(url: URL, private val attachment: Attachme
connected = true connected = true
} }
} }

View File

@ -153,7 +153,8 @@ data class ContractUpgradeWireTransaction(
listOf(legacyAttachment, upgradedAttachment), listOf(legacyAttachment, upgradedAttachment),
params, params,
id, id,
{ (services as ServiceHubCoreInternal).attachmentTrustCalculator.calculate(it) }) { transactionClassLoader -> { (services as ServiceHubCoreInternal).attachmentTrustCalculator.calculate(it) },
attachmentsClassLoaderCache = (services as ServiceHubCoreInternal).attachmentsClassLoaderCache) { transactionClassLoader ->
val resolvedInput = binaryInput.deserialize() val resolvedInput = binaryInput.deserialize()
val upgradedContract = upgradedContract(upgradedContractClassName, transactionClassLoader) val upgradedContract = upgradedContract(upgradedContractClassName, transactionClassLoader)
val outputState = calculateUpgradedState(resolvedInput, upgradedContract, upgradedAttachment) val outputState = calculateUpgradedState(resolvedInput, upgradedContract, upgradedAttachment)

View File

@ -26,6 +26,7 @@ import net.corda.core.internal.deserialiseComponentGroup
import net.corda.core.internal.isUploaderTrusted import net.corda.core.internal.isUploaderTrusted
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import java.util.Collections.unmodifiableList import java.util.Collections.unmodifiableList
@ -87,7 +88,8 @@ private constructor(
private val serializedInputs: List<SerializedStateAndRef>?, private val serializedInputs: List<SerializedStateAndRef>?,
private val serializedReferences: List<SerializedStateAndRef>?, private val serializedReferences: List<SerializedStateAndRef>?,
private val isAttachmentTrusted: (Attachment) -> Boolean, private val isAttachmentTrusted: (Attachment) -> Boolean,
private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier,
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
) : FullTransaction() { ) : FullTransaction() {
init { init {
@ -124,7 +126,8 @@ private constructor(
componentGroups: List<ComponentGroup>? = null, componentGroups: List<ComponentGroup>? = null,
serializedInputs: List<SerializedStateAndRef>? = null, serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null, serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
): LedgerTransaction { ): LedgerTransaction {
return LedgerTransaction( return LedgerTransaction(
inputs = inputs, inputs = inputs,
@ -141,7 +144,8 @@ private constructor(
serializedInputs = protect(serializedInputs), serializedInputs = protect(serializedInputs),
serializedReferences = protect(serializedReferences), serializedReferences = protect(serializedReferences),
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = ::BasicVerifier verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
} }
@ -176,7 +180,8 @@ private constructor(
serializedInputs = null, serializedInputs = null,
serializedReferences = null, serializedReferences = null,
isAttachmentTrusted = { true }, isAttachmentTrusted = { true },
verifierFactory = ::BasicVerifier verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = null
) )
} }
} }
@ -218,7 +223,8 @@ private constructor(
txAttachments, txAttachments,
getParamsWithGoo(), getParamsWithGoo(),
id, id,
isAttachmentTrusted = isAttachmentTrusted) { transactionClassLoader -> isAttachmentTrusted = isAttachmentTrusted,
attachmentsClassLoaderCache = attachmentsClassLoaderCache) { transactionClassLoader ->
// Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader]. // Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader].
// Only the copy will be used for verification, and the outer shell will be discarded. // Only the copy will be used for verification, and the outer shell will be discarded.
// This artifice is required to preserve backwards compatibility. // This artifice is required to preserve backwards compatibility.
@ -254,7 +260,8 @@ private constructor(
serializedInputs = serializedInputs, serializedInputs = serializedInputs,
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = alternateVerifier verifierFactory = alternateVerifier,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
// Read network parameters with backwards compatibility goo. // Read network parameters with backwards compatibility goo.
@ -320,7 +327,8 @@ private constructor(
serializedInputs = serializedInputs, serializedInputs = serializedInputs,
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
} else { } else {
// This branch is only present for backwards compatibility. // This branch is only present for backwards compatibility.
@ -704,7 +712,8 @@ private constructor(
serializedInputs = null, serializedInputs = null,
serializedReferences = null, serializedReferences = null,
isAttachmentTrusted = { it.isUploaderTrusted() }, isAttachmentTrusted = { it.isUploaderTrusted() },
verifierFactory = ::BasicVerifier verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = null
) )
@Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.") @Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.")
@ -733,7 +742,8 @@ private constructor(
serializedInputs = null, serializedInputs = null,
serializedReferences = null, serializedReferences = null,
isAttachmentTrusted = { it.isUploaderTrusted() }, isAttachmentTrusted = { it.isUploaderTrusted() },
verifierFactory = ::BasicVerifier verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = null
) )
@Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.") @Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.")
@ -761,7 +771,8 @@ private constructor(
serializedInputs = serializedInputs, serializedInputs = serializedInputs,
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
} }
@ -791,7 +802,8 @@ private constructor(
serializedInputs = serializedInputs, serializedInputs = serializedInputs,
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
) )
} }
} }

View File

@ -15,6 +15,7 @@ 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.internal.AttachmentsClassLoaderCache
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 java.security.PublicKey import java.security.PublicKey
@ -109,7 +110,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
services.networkParametersService.lookup(hashToResolve) services.networkParametersService.lookup(hashToResolve)
}, },
// `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal] // `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal]
isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true } isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true },
attachmentsClassLoaderCache = (services as? ServiceHubCoreInternal)?.attachmentsClassLoaderCache
) )
) )
} }
@ -145,7 +147,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveAttachment, resolveAttachment,
{ stateRef -> resolveStateRef(stateRef)?.serialize() }, { stateRef -> resolveStateRef(stateRef)?.serialize() },
{ null }, { null },
{ it.isUploaderTrusted() } { it.isUploaderTrusted() },
null
) )
} }
@ -161,16 +164,19 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveAttachment, resolveAttachment,
{ stateRef -> resolveStateRef(stateRef)?.serialize() }, { stateRef -> resolveStateRef(stateRef)?.serialize() },
resolveParameters, resolveParameters,
{ true } // Any attachment loaded through the DJVM should be trusted { true }, // Any attachment loaded through the DJVM should be trusted
null
) )
} }
@Suppress("LongParameterList", "ThrowsCount")
private fun toLedgerTransactionInternal( private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?, resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?, resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?, resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
resolveParameters: (SecureHash?) -> NetworkParameters?, resolveParameters: (SecureHash?) -> NetworkParameters?,
isAttachmentTrusted: (Attachment) -> Boolean isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
): LedgerTransaction { ): LedgerTransaction {
// Look up public keys to authenticated identities. // Look up public keys to authenticated identities.
val authenticatedCommands = commands.lazyMapped { cmd, _ -> val authenticatedCommands = commands.lazyMapped { cmd, _ ->
@ -206,7 +212,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
componentGroups, componentGroups,
serializedResolvedInputs, serializedResolvedInputs,
serializedResolvedReferences, serializedResolvedReferences,
isAttachmentTrusted isAttachmentTrusted,
attachmentsClassLoaderCache
) )
checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences) checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences)

View File

@ -4,6 +4,7 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.transactions.ComponentGroup import net.corda.core.transactions.ComponentGroup
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
@ -17,6 +18,7 @@ fun WireTransaction.accessGroupHashes() = this.groupHashes
fun WireTransaction.accessGroupMerkleRoots() = this.groupsMerkleRoots fun WireTransaction.accessGroupMerkleRoots() = this.groupsMerkleRoots
fun WireTransaction.accessAvailableComponentHashes() = this.availableComponentHashes fun WireTransaction.accessAvailableComponentHashes() = this.availableComponentHashes
@Suppress("LongParameterList")
fun createLedgerTransaction( fun createLedgerTransaction(
inputs: List<StateAndRef<ContractState>>, inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>, outputs: List<TransactionState<ContractState>>,
@ -31,8 +33,9 @@ fun createLedgerTransaction(
componentGroups: List<ComponentGroup>? = null, componentGroups: List<ComponentGroup>? = null,
serializedInputs: List<SerializedStateAndRef>? = null, serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null, serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean isAttachmentTrusted: (Attachment) -> Boolean,
): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted) attachmentsClassLoaderCache: AttachmentsClassLoaderCache
): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache)
fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause) fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause)
fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause) fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)

View File

@ -60,6 +60,8 @@ import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days import net.corda.core.utilities.days
@ -317,6 +319,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} else { } else {
BasicVerifierFactoryService() BasicVerifierFactoryService()
} }
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory).tokenize()
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize() val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
val auditService = DummyAuditService().tokenize() val auditService = DummyAuditService().tokenize()
@Suppress("LeakingThis") @Suppress("LeakingThis")
@ -1170,6 +1173,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private lateinit var _myInfo: NodeInfo private lateinit var _myInfo: NodeInfo
override val myInfo: NodeInfo get() = _myInfo override val myInfo: NodeInfo get() = _myInfo
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache get() = this@AbstractNode.attachmentsClassLoaderCache
private lateinit var _networkParameters: NetworkParameters private lateinit var _networkParameters: NetworkParameters
override val networkParameters: NetworkParameters get() = _networkParameters override val networkParameters: NetworkParameters get() = _networkParameters

View File

@ -37,6 +37,7 @@ class MigrationNamedCacheFactory(private val metricRegistry: MetricRegistry?,
"NodeAttachmentService_contractAttachmentVersions" -> caffeine.maximumSize(defaultCacheSize) "NodeAttachmentService_contractAttachmentVersions" -> caffeine.maximumSize(defaultCacheSize)
"NodeParametersStorage_networkParametersByHash" -> caffeine.maximumSize(defaultCacheSize) "NodeParametersStorage_networkParametersByHash" -> caffeine.maximumSize(defaultCacheSize)
"NodeAttachmentTrustCalculator_trustedKeysCache" -> caffeine.maximumSize(defaultCacheSize) "NodeAttachmentTrustCalculator_trustedKeysCache" -> caffeine.maximumSize(defaultCacheSize)
"AttachmentsClassLoader_cache" -> caffeine.maximumSize(defaultCacheSize)
else -> throw IllegalArgumentException("Unexpected cache name $name.") else -> throw IllegalArgumentException("Unexpected cache name $name.")
} }
} }

View File

@ -15,6 +15,8 @@ import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.transactions.ContractUpgradeLedgerTransaction import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.NotaryChangeLedgerTransaction import net.corda.core.transactions.NotaryChangeLedgerTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
@ -62,6 +64,8 @@ class MigrationServicesForResolution(
cacheFactory cacheFactory
) )
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory)
private fun defaultNetworkParameters(): NetworkParameters { private fun defaultNetworkParameters(): NetworkParameters {
logger.warn("Using a dummy set of network parameters for migration.") logger.warn("Using a dummy set of network parameters for migration.")
val clock = Clock.systemUTC() val clock = Clock.systemUTC()
@ -124,7 +128,8 @@ class MigrationServicesForResolution(
networkParameters, networkParameters,
tx.id, tx.id,
attachmentTrustCalculator::calculate, attachmentTrustCalculator::calculate,
cordappLoader.appClassLoader) { cordappLoader.appClassLoader,
attachmentsClassLoaderCache) {
deserialiseComponentGroup(tx.componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true) deserialiseComponentGroup(tx.componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
} }
states.filterIndexed {index, _ -> stateIndices.contains(index)}.toList() states.filterIndexed {index, _ -> stateIndices.contains(index)}.toList()

View File

@ -63,6 +63,7 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
name == "NodeParametersStorage_networkParametersByHash" -> caffeine.maximumSize(defaultCacheSize) name == "NodeParametersStorage_networkParametersByHash" -> caffeine.maximumSize(defaultCacheSize)
name == "PublicKeyToOwningIdentityCache_cache" -> caffeine.maximumSize(defaultCacheSize) name == "PublicKeyToOwningIdentityCache_cache" -> caffeine.maximumSize(defaultCacheSize)
name == "NodeAttachmentTrustCalculator_trustedKeysCache" -> caffeine.maximumSize(defaultCacheSize) name == "NodeAttachmentTrustCalculator_trustedKeysCache" -> caffeine.maximumSize(defaultCacheSize)
name == "AttachmentsClassLoader_cache" -> caffeine.maximumSize(defaultAttachmentsClassLoaderCacheSize)
else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?") else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
} }
} }
@ -85,4 +86,6 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
} }
open protected val defaultCacheSize = 1024L open protected val defaultCacheSize = 1024L
private val defaultAttachmentsClassLoaderCacheSize = defaultCacheSize / CACHE_SIZE_DENOMINATOR
} }
private const val CACHE_SIZE_DENOMINATOR = 4L

View File

@ -50,13 +50,13 @@ class FinalityHandlerTest {
getOrThrow() getOrThrow()
} }
bob.assertFlowSentForObservationDueToConstraintError(finalityHandlerId) bob.assertFlowSentForObservationDueToUntrustedAttachmentsException(finalityHandlerId)
assertThat(bob.getTransaction(stx.id)).isNull() assertThat(bob.getTransaction(stx.id)).isNull()
bob = mockNet.restartNode(bob) bob = mockNet.restartNode(bob)
// Since we've not done anything to fix the orignal error, we expect the finality handler to be sent to the hospital // Since we've not done anything to fix the orignal error, we expect the finality handler to be sent to the hospital
// again on restart // again on restart
bob.assertFlowSentForObservationDueToConstraintError(finalityHandlerId) bob.assertFlowSentForObservationDueToUntrustedAttachmentsException(finalityHandlerId)
assertThat(bob.getTransaction(stx.id)).isNull() assertThat(bob.getTransaction(stx.id)).isNull()
} }
@ -96,7 +96,7 @@ class FinalityHandlerTest {
.ofType(R::class.java) .ofType(R::class.java)
} }
private fun TestStartedNode.assertFlowSentForObservationDueToConstraintError(runId: StateMachineRunId) { private fun TestStartedNode.assertFlowSentForObservationDueToUntrustedAttachmentsException(runId: StateMachineRunId) {
val observation = medicalRecordsOfType<MedicalRecord.Flow>() val observation = medicalRecordsOfType<MedicalRecord.Flow>()
.filter { it.flowId == runId } .filter { it.flowId == runId }
.toBlocking() .toBlocking()
@ -104,7 +104,7 @@ class FinalityHandlerTest {
assertThat(observation.outcome).isEqualTo(Outcome.OVERNIGHT_OBSERVATION) assertThat(observation.outcome).isEqualTo(Outcome.OVERNIGHT_OBSERVATION)
assertThat(observation.by).contains(FinalityDoctor) assertThat(observation.by).contains(FinalityDoctor)
val error = observation.errors.single() val error = observation.errors.single()
assertThat(error).isInstanceOf(TransactionVerificationException.ContractConstraintRejection::class.java) assertThat(error).isInstanceOf(TransactionVerificationException.UntrustedAttachmentsException::class.java)
} }
private fun TestStartedNode.getTransaction(id: SecureHash): SignedTransaction? { private fun TestStartedNode.getTransaction(id: SecureHash): SignedTransaction? {

View File

@ -14,6 +14,8 @@ 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.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
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
@ -130,6 +132,7 @@ data class TestTransactionDSLInterpreter private constructor(
ledgerInterpreter.services.cordappProvider ledgerInterpreter.services.cordappProvider
override val notaryService: NotaryService? = null override val notaryService: NotaryService? = null
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
} }
private fun copy(): TestTransactionDSLInterpreter = private fun copy(): TestTransactionDSLInterpreter =

View File

@ -26,6 +26,7 @@ class TestingNamedCacheFactory private constructor(private val sizeOverride: Lon
val configuredCaffeine = when (name) { val configuredCaffeine = when (name) {
"DBTransactionStorage_transactions" -> caffeine.maximumWeight(1.MB) "DBTransactionStorage_transactions" -> caffeine.maximumWeight(1.MB)
"NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(1.MB) "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(1.MB)
"AttachmentsClassLoader_cache" -> caffeine.maximumSize(sizeOverride)
else -> caffeine.maximumSize(sizeOverride) else -> caffeine.maximumSize(sizeOverride)
} }
return configuredCaffeine.build<K, V>(loader) return configuredCaffeine.build<K, V>(loader)