mirror of
https://github.com/corda/corda.git
synced 2025-01-31 00:24:59 +00:00
CORDA-4125: Backport AttachmentClassLoader updates to 4.4 to enable ENT-6152 backport (#6878)
* CORDA-3755: Switched attachments map to a WeakHashMap (#6214) * Bump OS release version 4.6 * CORDA-3755: Switched attachments map to a WeakHashMap * CORDA-3755: Added explicit strong references to map key. * CORDA-3755: Keeping detekt happy. * CORDA-3755: Test a gc in verify. * CORDA-3755: Making detekt happy. * CORDA-3755: Suppress warnings for weak reference test. * CORDA-3755: Fixing build failure with attachments. * CORDA-3755: Rewrite based on Ricks input - now handles attachment already existing in map! * CORDA-3755: Refactor WeakReference behaviour into AttachmentsHolderImpl and provide alternate version of this class for core-deterministic. * CORDA-3755: Added more tests for WeakHashMap. * CORDA-3755: Ignore the tests using System.gc keep for local testing only * CORDA-3755: Adding comment to explain the ignored tests. * Make AttachmentsHolderImpl package-private inside core-deterministic, just like it is inside core. * CORDA-3755: Update assertions following review comments. * CORDA-3755: Removing import * CORDA-3755: Removed unused var. * CORDA-3755: Reverting files that somehow got changed in rebase. Co-authored-by: nargas-ritu <ritu.gupta@r3.com> Co-authored-by: Chris Rankin <chris.rankin@r3.com> * CORDA-3769: Switched attachments class loader cache to use caffeine (#6326) * CORDA-3769: Switched attachments class loader cache to use caffeine with original implementation used by determinstic core. * CORDA-3769: Removed default ctor arguments. * CORDA-3769: Switched mapping function to Function type to avoid synthetic method being generated. * CORDA-3769: Now using a cache created from NamedCacheFactory for the attachments class loader cache. * CORDA-3769: Making detekt happy. * CORDA-3769: The finality tests now check for UntrustedAttachmentsException which will actually happen in reality. * CORDA-3769: Refactored after review comments. * CORDA-3769: Removed the AttachmentsClassLoaderSimpleCacheImpl as DJVM does not need it. Also updated due to review comments. * CORDA-3769: Removed the generic parameters from AttachmentsClassLoader. * CORDA-3769: Removed unused imports. * CORDA-3769: Updates from review comments. * CORDA-3769: Updated following review comments. MigrationServicesForResolution now uses cache factory. Ctor updated for AttachmentsClassLoaderSimpleCacheImpl. * CORDA-3769: Reduced max class loader cache size * CORDA-3769: Fixed the attachments class loader cache size to a fixed default * CORDA-3769: Switched attachments class loader size to be reduced by fixed value. * CORDA-4125: Parameter has been added to a private ctor. Co-authored-by: nargas-ritu <ritu.gupta@r3.com> Co-authored-by: Chris Rankin <chris.rankin@r3.com>
This commit is contained in:
parent
90238f22a0
commit
360b3f8d0c
@ -6160,7 +6160,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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
@ -500,6 +501,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,
|
||||||
@ -511,7 +513,8 @@ class AttachmentsClassLoaderTests {
|
|||||||
privacySalt,
|
privacySalt,
|
||||||
testNetworkParameters(),
|
testNetworkParameters(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
isAttachmentTrusted = { true }
|
isAttachmentTrusted = { true },
|
||||||
|
attachmentsClassLoaderCache = attachmentsClassLoaderCache
|
||||||
)
|
)
|
||||||
transaction.verify()
|
transaction.verify()
|
||||||
}
|
}
|
||||||
|
@ -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.testing.internal.rigorousMock
|
import net.corda.testing.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() }
|
||||||
|
@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
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.
|
||||||
@ -15,6 +16,8 @@ interface ServiceHubCoreInternal : ServiceHub {
|
|||||||
val attachmentTrustCalculator: AttachmentTrustCalculator
|
val attachmentTrustCalculator: AttachmentTrustCalculator
|
||||||
|
|
||||||
fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver
|
fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver
|
||||||
|
|
||||||
|
val attachmentsClassLoaderCache: AttachmentsClassLoaderCache
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TransactionsResolver {
|
interface TransactionsResolver {
|
||||||
|
@ -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
|
||||||
@ -19,7 +22,9 @@ import java.io.IOException
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.net.*
|
import java.net.*
|
||||||
|
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 +293,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 = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
|
val serializers = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
|
||||||
@ -329,7 +330,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) {
|
||||||
@ -385,14 +386,6 @@ object AttachmentURLStreamHandlerFactory : URLStreamHandlerFactory {
|
|||||||
if (url.protocol != attachmentScheme) throw IllegalArgumentException("Cannot handle protocol: ${url.protocol}")
|
if (url.protocol != attachmentScheme) throw IllegalArgumentException("Cannot handle protocol: ${url.protocol}")
|
||||||
return url.file.hashCode()
|
return url.file.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AttachmentURLConnection(url: URL, private val attachment: Attachment) : URLConnection(url) {
|
|
||||||
override fun getContentLengthLong(): Long = attachment.size.toLong()
|
|
||||||
override fun getInputStream(): InputStream = attachment.open()
|
|
||||||
override fun connect() {
|
|
||||||
connected = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,3 +413,51 @@ private class AttachmentsHolderImpl : AttachmentsHolder {
|
|||||||
attachments[key] = WeakReference(key) to value
|
attachments[key] = WeakReference(key) to value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
override fun getContentLengthLong(): Long = attachment.size.toLong()
|
||||||
|
override fun getInputStream(): InputStream = attachment.open()
|
||||||
|
/**
|
||||||
|
* Define the permissions that [AttachmentsClassLoader] will need to
|
||||||
|
* use this [URL]. The attachment is stored in memory, and so we
|
||||||
|
* don't need any extra permissions here. But if we don't override
|
||||||
|
* [getPermission] then [AttachmentsClassLoader] will assign the
|
||||||
|
* default permission of ALL_PERMISSION to these classes'
|
||||||
|
* [java.security.ProtectionDomain]. This would be a security hole!
|
||||||
|
*/
|
||||||
|
override fun getPermission(): Permission? = null
|
||||||
|
override fun connect() {
|
||||||
|
connected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -5,13 +5,20 @@ import net.corda.core.contracts.ContractAttachment
|
|||||||
import net.corda.core.contracts.ContractClassName
|
import net.corda.core.contracts.ContractClassName
|
||||||
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.services.AttachmentId
|
||||||
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory
|
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory
|
||||||
import net.corda.core.serialization.internal.AttachmentsClassLoader
|
import net.corda.core.serialization.internal.AttachmentsClassLoader
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertSame
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.lang.ref.ReferenceQueue
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.jar.JarOutputStream
|
import java.util.jar.JarOutputStream
|
||||||
@ -120,9 +127,125 @@ class ClassLoadingUtilsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun signedAttachment(data: ByteArray, vararg parties: Party) = ContractAttachment.create(
|
@Ignore("Using System.gc in this test which has no guarantees when/if gc occurs.")
|
||||||
|
@Test(timeout=300_000)
|
||||||
|
@Suppress("ExplicitGarbageCollectionCall", "UNUSED_VALUE")
|
||||||
|
fun `test weak reference removed from map`() {
|
||||||
|
val jarData = with(ByteArrayOutputStream()) {
|
||||||
|
val internalName = STANDALONE_CLASS_NAME.asInternalName
|
||||||
|
JarOutputStream(this, Manifest()).use {
|
||||||
|
it.setLevel(NO_COMPRESSION)
|
||||||
|
it.setMethod(DEFLATED)
|
||||||
|
it.putNextEntry(directoryEntry("com"))
|
||||||
|
it.putNextEntry(directoryEntry("com/example"))
|
||||||
|
it.putNextEntry(classEntry(internalName))
|
||||||
|
it.write(TemplateClassWithEmptyConstructor::class.java.renameTo(internalName))
|
||||||
|
}
|
||||||
|
toByteArray()
|
||||||
|
}
|
||||||
|
val attachment = signedAttachment(jarData)
|
||||||
|
var url: URL? = AttachmentURLStreamHandlerFactory.toUrl(attachment)
|
||||||
|
|
||||||
|
val referenceQueue: ReferenceQueue<URL> = ReferenceQueue()
|
||||||
|
val weakReference = WeakReference<URL>(url, referenceQueue)
|
||||||
|
|
||||||
|
assertEquals(1, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
// Clear strong reference
|
||||||
|
url = null
|
||||||
|
System.gc()
|
||||||
|
val ref = referenceQueue.remove(100000)
|
||||||
|
assertSame(weakReference, ref)
|
||||||
|
assertEquals(0, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore("Using System.gc in this test which has no guarantees when/if gc occurs.")
|
||||||
|
@Test(timeout=300_000)
|
||||||
|
@Suppress("ExplicitGarbageCollectionCall", "UNUSED_VALUE")
|
||||||
|
fun `test adding same attachment twice then removing`() {
|
||||||
|
val jarData = with(ByteArrayOutputStream()) {
|
||||||
|
val internalName = STANDALONE_CLASS_NAME.asInternalName
|
||||||
|
JarOutputStream(this, Manifest()).use {
|
||||||
|
it.setLevel(NO_COMPRESSION)
|
||||||
|
it.setMethod(DEFLATED)
|
||||||
|
it.putNextEntry(directoryEntry("com"))
|
||||||
|
it.putNextEntry(directoryEntry("com/example"))
|
||||||
|
it.putNextEntry(classEntry(internalName))
|
||||||
|
it.write(TemplateClassWithEmptyConstructor::class.java.renameTo(internalName))
|
||||||
|
}
|
||||||
|
toByteArray()
|
||||||
|
}
|
||||||
|
val attachment1 = signedAttachment(jarData)
|
||||||
|
val attachment2 = signedAttachment(jarData)
|
||||||
|
var url1: URL? = AttachmentURLStreamHandlerFactory.toUrl(attachment1)
|
||||||
|
var url2: URL? = AttachmentURLStreamHandlerFactory.toUrl(attachment2)
|
||||||
|
|
||||||
|
val referenceQueue1: ReferenceQueue<URL> = ReferenceQueue()
|
||||||
|
val weakReference1 = WeakReference<URL>(url1, referenceQueue1)
|
||||||
|
|
||||||
|
val referenceQueue2: ReferenceQueue<URL> = ReferenceQueue()
|
||||||
|
val weakReference2 = WeakReference<URL>(url2, referenceQueue2)
|
||||||
|
|
||||||
|
assertEquals(1, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
url1 = null
|
||||||
|
System.gc()
|
||||||
|
val ref1 = referenceQueue1.remove(500)
|
||||||
|
assertNull(ref1)
|
||||||
|
assertEquals(1, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
|
||||||
|
url2 = null
|
||||||
|
System.gc()
|
||||||
|
val ref2 = referenceQueue2.remove(100000)
|
||||||
|
assertSame(weakReference2, ref2)
|
||||||
|
assertSame(weakReference1, referenceQueue1.poll())
|
||||||
|
assertEquals(0, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore("Using System.gc in this test which has no guarantees when/if gc occurs.")
|
||||||
|
@Test(timeout=300_000)
|
||||||
|
@Suppress("ExplicitGarbageCollectionCall", "UNUSED_VALUE")
|
||||||
|
fun `test adding two different attachments then removing`() {
|
||||||
|
val jarData1 = with(ByteArrayOutputStream()) {
|
||||||
|
val internalName = STANDALONE_CLASS_NAME.asInternalName
|
||||||
|
JarOutputStream(this, Manifest()).use {
|
||||||
|
it.setLevel(NO_COMPRESSION)
|
||||||
|
it.setMethod(DEFLATED)
|
||||||
|
it.putNextEntry(directoryEntry("com"))
|
||||||
|
it.putNextEntry(directoryEntry("com/example"))
|
||||||
|
it.putNextEntry(classEntry(internalName))
|
||||||
|
it.write(TemplateClassWithEmptyConstructor::class.java.renameTo(internalName))
|
||||||
|
}
|
||||||
|
toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val attachment1 = signedAttachment(jarData1)
|
||||||
|
val attachment2 = signedAttachment(jarData1, id = SecureHash.randomSHA256())
|
||||||
|
var url1: URL? = AttachmentURLStreamHandlerFactory.toUrl(attachment1)
|
||||||
|
var url2: URL? = AttachmentURLStreamHandlerFactory.toUrl(attachment2)
|
||||||
|
|
||||||
|
val referenceQueue1: ReferenceQueue<URL> = ReferenceQueue()
|
||||||
|
val weakReference1 = WeakReference<URL>(url1, referenceQueue1)
|
||||||
|
|
||||||
|
val referenceQueue2: ReferenceQueue<URL> = ReferenceQueue()
|
||||||
|
val weakReference2 = WeakReference<URL>(url2, referenceQueue2)
|
||||||
|
|
||||||
|
assertEquals(2, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
url1 = null
|
||||||
|
System.gc()
|
||||||
|
val ref1 = referenceQueue1.remove(100000)
|
||||||
|
assertSame(weakReference1, ref1)
|
||||||
|
assertEquals(1, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
|
||||||
|
url2 = null
|
||||||
|
System.gc()
|
||||||
|
val ref2 = referenceQueue2.remove(100000)
|
||||||
|
assertSame(weakReference2, ref2)
|
||||||
|
assertEquals(0, AttachmentURLStreamHandlerFactory.loadedAttachmentsSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun signedAttachment(data: ByteArray, id: AttachmentId = contractAttachmentId,
|
||||||
|
vararg parties: Party) = ContractAttachment.create(
|
||||||
object : AbstractAttachment({ data }, "test") {
|
object : AbstractAttachment({ data }, "test") {
|
||||||
override val id: SecureHash get() = contractAttachmentId
|
override val id: SecureHash get() = id
|
||||||
|
|
||||||
override val signerKeys: List<PublicKey> get() = parties.map(Party::owningKey)
|
override val signerKeys: List<PublicKey> get() = parties.map(Party::owningKey)
|
||||||
}, PROGRAM_ID, signerKeys = parties.map(Party::owningKey)
|
}, PROGRAM_ID, signerKeys = parties.map(Party::owningKey)
|
||||||
|
@ -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)
|
||||||
|
@ -57,6 +57,8 @@ import net.corda.core.schemas.MappedSchema
|
|||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
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
|
||||||
@ -315,6 +317,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")
|
||||||
@ -1144,6 +1147,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
|
||||||
|
|
||||||
|
@ -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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
@ -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? {
|
||||||
|
@ -13,6 +13,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
|
||||||
@ -127,6 +129,8 @@ data class TestTransactionDSLInterpreter private constructor(
|
|||||||
|
|
||||||
override val cordappProvider: CordappProvider =
|
override val cordappProvider: CordappProvider =
|
||||||
ledgerInterpreter.services.cordappProvider
|
ledgerInterpreter.services.cordappProvider
|
||||||
|
|
||||||
|
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copy(): TestTransactionDSLInterpreter =
|
private fun copy(): TestTransactionDSLInterpreter =
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user