Contract constraints (#1518)

* Contract constraints and attachment loading

Fix compiler warnings.

Fixed IdentitySyncFlowTests in confidential-identities.

Fixes.

Fix AttachmentClassLoaderTests.

Added a TODO.

Renamed cordapp service.

Fix compilation error in java code.

Fix RaftNotaryServiceTests

Fix AttachmentLoadingTest

Fix DistributedServiceTests and LargeTransactionTests.

Add cordapp packages to Verifier tests.

Refactor DummyContractBackdoor back out of internal package.

Resolve compiler warnings.

Consolidate excluding `isolated` project at top-level.

Fix contract attachment serialisation for remote verifier.

Fix integration tests for client:rpc.

Contract constraints and attachment loading

Fix compiler warnings.

Fixed IdentitySyncFlowTests in confidential-identities.

Fixes.

Fix AttachmentClassLoaderTests.

Added a TODO.

Renamed cordapp service.

Fix compilation error in java code.

Fix example compilation.

Fix RaftNotaryServiceTests

Fix AttachmentLoadingTest

Fix DistributedServiceTests and LargeTransactionTests.

Add cordapp packages to Verifier tests.

Refactor DummyContractBackdoor back out of internal package.

Resolve compiler warnings.

Consolidate excluding `isolated` project at top-level.

Fix integration tests for client:rpc.

Fixed issues with node driver and differing ZIPs.

Review changes.

Refactor GeneratedAttachment into node-api module.

Merge branch 'clint/hash-constraint' of https://github.com/corda/corda into clint/hash-constraint

Fixed compile error following rebase.

wip - test to check that app code isn't loaded from attachments sent over the wire.

Use Kotlin copyTo() rather than Apache's IOUtils.

Fixes

more fixes.

Removing unconstrained output.

More fixes.

Fixed another test.

Added missing plugin definition in net.corda.core.node.CordaPluginRegistry: net.corda.finance.contracts.isolated.IsolatedPlugin

Re-added missing magic string used in unit test.

Remove unused FlowSession variable.

* Review fixes.

* More review fixes.

* Moved Cordapp implementation to an internal package.

* More JVMOverloads.
This commit is contained in:
Clinton
2017-09-25 17:05:18 +01:00
committed by josecoll
parent 2a7da1eb47
commit 532bbb5cca
116 changed files with 1601 additions and 599 deletions

View File

@ -10,6 +10,7 @@ import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.BitSetSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.PartyAndCertificate
@ -38,6 +39,7 @@ import org.slf4j.Logger
import sun.security.ec.ECPublicKeyImpl
import sun.security.provider.certpath.X509CertPath
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.io.InputStream
import java.lang.reflect.Modifier.isPublic
@ -113,6 +115,9 @@ object DefaultKryoCustomizer {
// Don't deserialize PrivacySalt via its default constructor.
register(PrivacySalt::class.java, PrivacySaltSerializer)
// Used by the remote verifier, and will possibly be removed in future.
register(ContractAttachment::class.java, ContractAttachmentSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
}
@ -170,4 +175,18 @@ object DefaultKryoCustomizer {
return PrivacySalt(input.readBytesWithLength())
}
}
private object ContractAttachmentSerializer : Serializer<ContractAttachment>() {
override fun write(kryo: Kryo, output: Output, obj: ContractAttachment) {
val buffer = ByteArrayOutputStream()
obj.attachment.open().use { it.copyTo(buffer) }
output.writeBytesWithLength(buffer.toByteArray())
output.writeString(obj.contract)
}
override fun read(kryo: Kryo, input: Input, type: Class<ContractAttachment>): ContractAttachment {
val attachment = GeneratedAttachment(input.readBytesWithLength())
return ContractAttachment(attachment, input.readString())
}
}
}

View File

@ -616,4 +616,4 @@ class ThrowableSerializer<T>(kryo: Kryo, type: Class<T>) : Serializer<Throwable>
}
private fun Throwable.setSuppressedToSentinel() = suppressedField.set(this, sentinelValue)
}
}

View File

@ -60,7 +60,7 @@ data class SerializationContextImpl(override val preferredSerializationVersion:
serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id }
}
missing.isNotEmpty() && throw MissingAttachmentsException(missing)
AttachmentsClassLoader(attachments)
AttachmentsClassLoader(attachments, parent = deserializationClassLoader)
})
} catch (e: ExecutionException) {
// Caught from within the cache get, so unwrap.

View File

@ -0,0 +1,17 @@
package net.corda.nodeapi
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PartyAndReference
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
/**
* This interface deliberately mirrors the one in the finance:isolated module.
* We will actually link [AnotherDummyContract] against this interface rather
* than the one inside isolated.jar, which means we won't need to use reflection
* to execute the contract's generateInitial() method.
*/
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}

View File

@ -0,0 +1,74 @@
package net.corda.nodeapi.internal
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.*
import net.corda.testing.node.MockServices
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
class AttachmentsClassLoaderStaticContractTests : TestDependencyInjectionBase() {
class AttachmentDummyContract : Contract {
companion object {
private val ATTACHMENT_PROGRAM_ID = "net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract"
}
data class State(val magicNumber: Int = 0) : ContractState {
override val participants: List<AbstractParty>
get() = listOf()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction) {
// Always accepts.
}
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary)
.withItems(StateAndContract(state, ATTACHMENT_PROGRAM_ID), Command(Commands.Create(), owner.party.owningKey))
}
}
private lateinit var serviceHub: MockServices
@Before
fun `create service hub`() {
serviceHub = MockServices(cordappPackages=listOf("net.corda.nodeapi.internal"))
}
@After
fun `clear packages`() {
unsetCordappPackages()
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = AttachmentDummyContract().generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction(serviceHub)
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract")
val contract = contractClass.newInstance() as Contract
assertNotNull(contract)
}
}

View File

@ -1,29 +1,30 @@
package net.corda.nodeapi
package net.corda.nodeapi.internal
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.declaredField
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.AttachmentsClassLoader
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.DummyContractBackdoor
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName
import net.corda.nodeapi.internal.serialization.withTokenContext
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MEGA_CORP
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.kryoSpecific
import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.node.MockServices
import org.apache.commons.io.IOUtils
import org.junit.Assert
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@ -31,83 +32,72 @@ import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}
class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar")
private val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
private val ATTACHMENT_PROGRAM_ID = "net.corda.nodeapi.AttachmentsClassLoaderTests.AttachmentDummyContract"
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext {
val serviceHub = mock<ServiceHub>()
whenever(serviceHub.attachments).thenReturn(attachmentStorage)
return this.withServiceHub(serviceHub)
}
private fun SerializationContext.withServiceHub(serviceHub: ServiceHub): SerializationContext {
return this.withTokenContext(SerializeAsTokenContextImpl(serviceHub) {}).withProperty(attachmentsClassLoaderEnabledPropertyName, true)
}
}
class AttachmentDummyContract : Contract {
data class State(val magicNumber: Int = 0) : ContractState {
override val participants: List<AbstractParty>
get() = listOf()
}
private lateinit var serviceHub: DummyServiceHub
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
class DummyServiceHub : MockServices() {
override val cordappProvider: CordappProviderImpl
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH))).start(attachments)
override fun verify(tx: LedgerTransaction) {
// Always accepts.
}
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary).withItems(StateAndContract(state, ATTACHMENT_PROGRAM_ID), Command(Commands.Create(), owner.party.owningKey))
}
private val cordapp get() = cordappProvider.cordapps.first()
val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
val appContext get() = cordappProvider.getAppContext(cordapp)
}
private fun importJar(storage: AttachmentStorage) = ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) }
// These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
// the class may be on the unit test class path (due to default IDE settings, etc), it won't be loaded into the
// regular app classloader but rather than ClassLoaderForTests. This helps keep our environment clean and
// ensures we have precise control over where it's loaded.
object FilteringClassLoader : ClassLoader() {
override fun loadClass(name: String, resolve: Boolean): Class<*>? {
return if ("AnotherDummyContract" in name) {
null
} else
super.loadClass(name, resolve)
@Throws(ClassNotFoundException::class)
override fun loadClass(name: String, resolve: Boolean): Class<*> {
if ("AnotherDummyContract" in name) {
throw ClassNotFoundException(name)
}
return super.loadClass(name, resolve)
}
}
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
@Before
fun `create service hub`() {
serviceHub = DummyServiceHub()
}
@Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
val child = ClassLoaderForTests()
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as Contract
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as Contract
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
}
}
private fun fakeAttachment(filepath: String, content: String): ByteArray {
val bs = ByteArrayOutputStream()
val js = JarOutputStream(bs)
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
js.close()
JarOutputStream(bs).use { js ->
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
}
return bs.toByteArray()
}
@ -116,13 +106,12 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
attachment.extractFile(filepath, it)
return it.toByteArray()
}
}
@Test
fun `test MockAttachmentStorage open as jar`() {
val storage = MockAttachmentStorage()
val key = importJar(storage)
val storage = serviceHub.attachments
val key = serviceHub.attachmentId
val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR()
@ -132,9 +121,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `test overlapping file exception`() {
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data")))
@ -145,9 +134,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `basic`() {
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -164,24 +153,23 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data")))
val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt")
Assert.assertArrayEquals("some data".toByteArray(), data1a)
assertArrayEquals("some data".toByteArray(), data1a)
val data1b = readAttachment(storage.openAttachment(att1)!!, "\\folder1\\foldera\\file1.txt")
Assert.assertArrayEquals("some data".toByteArray(), data1b)
assertArrayEquals("some data".toByteArray(), data1b)
val data2a = readAttachment(storage.openAttachment(att2)!!, "\\folder1\\folderb\\file2.txt")
Assert.assertArrayEquals("some other data".toByteArray(), data2a)
assertArrayEquals("some other data".toByteArray(), data2a)
val data2b = readAttachment(storage.openAttachment(att2)!!, "/folder1/folderb/file2.txt")
Assert.assertArrayEquals("some other data".toByteArray(), data2b)
assertArrayEquals("some other data".toByteArray(), data2b)
}
@Test
fun `loading class AnotherDummyContract`() {
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -192,18 +180,11 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
}
@Test
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("net.corda.nodeapi.AttachmentsClassLoaderTests\$AttachmentDummyContract")
val contract = contractClass.newInstance() as Contract
assertNotNull(contract)
}
private fun createContract2Cash(): Contract {
val cl = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl)
return contractClass.newInstance() as Contract
ClassLoaderForTests().use { cl ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl)
return contractClass.newInstance() as Contract
}
}
@Test
@ -212,9 +193,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
val bytes = contract.serialize()
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -240,9 +221,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
val bytes = data.serialize(context = context2)
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -291,39 +272,26 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
assertEquals(bytesSequence, copiedBytesSequence)
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = AttachmentDummyContract().generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test
fun `test serialization of WireTransaction with dynamically loaded contract`() {
val child = ClassLoaderForTests()
val child = serviceHub.appContext.classLoader
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val context = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(contract.javaClass)
val context = SerializationFactory.defaultFactory.defaultContext
.withWhitelisted(contract.javaClass)
.withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$State", true, child))
.withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$Commands\$Create", true, child))
.withAttachmentStorage(storage)
.withServiceHub(serviceHub)
.withClassLoader(child)
// todo - think about better way to push attachmentStorage down to serializer
val bytes = run {
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction(serializationContext = context)
wireTransaction.serialize()
val wireTransaction = tx.toWireTransaction(serviceHub, context)
wireTransaction.serialize(context = context)
}
val copiedWireTransaction = bytes.deserialize(context = context)
assertEquals(1, copiedWireTransaction.outputs.size)
// Contracts need to be loaded by the same classloader as the ContractState itself
// Contracts need to be loaded by the same classloader as the ContractState itself
val contractClassloader = copiedWireTransaction.getOutput(0).javaClass.classLoader
val contract2 = contractClassloader.loadClass(copiedWireTransaction.outputs.first().contract).newInstance() as DummyContractBackdoor
assertEquals(contract2.javaClass.classLoader, copiedWireTransaction.outputs[0].data.javaClass.classLoader)
@ -332,82 +300,82 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `test deserialize of WireTransaction where contract cannot be found`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val context = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(contract.javaClass)
.withWhitelisted(Class.forName("net.corda.finance.contracts.isolated.AnotherDummyContract\$State", true, child))
.withWhitelisted(Class.forName("net.corda.finance.contracts.isolated.AnotherDummyContract\$Commands\$Create", true, child))
.withAttachmentStorage(storage)
kryoSpecific<AttachmentsClassLoaderTests>("Kryo verifies/loads attachments on deserialization, whereas AMQP currently does not") {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
// todo - think about better way to push attachmentStorage down to serializer
val attachmentRef = importJar(storage)
val bytes = run {
val attachmentRef = serviceHub.attachmentId
val bytes = run {
val outboundContext = SerializationFactory.defaultFactory.defaultContext
.withServiceHub(serviceHub)
.withClassLoader(child)
val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext)
wireTransaction.serialize(context = outboundContext)
}
// use empty attachmentStorage
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val e = assertFailsWith(MissingAttachmentsException::class) {
val mockAttStorage = MockAttachmentStorage()
val inboundContext = SerializationFactory.defaultFactory.defaultContext
.withAttachmentStorage(mockAttStorage)
.withAttachmentsClassLoader(listOf(attachmentRef))
bytes.deserialize(context = inboundContext)
val wireTransaction = tx.toWireTransaction(serializationContext = context)
wireTransaction.serialize()
}
// use empty attachmentStorage
val e = assertFailsWith(MissingAttachmentsException::class) {
val mockAttStorage = MockAttachmentStorage()
bytes.deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentStorage(mockAttStorage))
if(mockAttStorage.openAttachment(attachmentRef) == null) {
throw MissingAttachmentsException(listOf(attachmentRef))
if (mockAttStorage.openAttachment(attachmentRef) == null) {
throw MissingAttachmentsException(listOf(attachmentRef))
}
}
assertEquals(attachmentRef, e.ids.single())
}
}
assertEquals(attachmentRef, e.ids.single())
}
@Test
fun `test loading a class from attachment during deserialization`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val storage = MockAttachmentStorage()
val attachmentRef = importJar(storage)
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withAttachmentStorage(storage)
.withAttachmentsClassLoader(listOf(attachmentRef))
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
serialized.deserialize(context = inboundContext)
}
@Test
fun `test loading a class with attachment missing during deserialization`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val storage = MockAttachmentStorage()
val attachmentRef = SecureHash.randomSHA256()
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// Serialize with custom context to avoid populating the default context with the specially loaded class.
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment.
val e = assertFailsWith(MissingAttachmentsException::class) {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
val attachmentRef = serviceHub.attachmentId
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withAttachmentStorage(storage)
.withServiceHub(serviceHub)
.withAttachmentsClassLoader(listOf(attachmentRef))
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
serialized.deserialize(context = inboundContext)
}
assertEquals(attachmentRef, e.ids.single())
}
@Test
fun `test loading a class with attachment missing during deserialization`() {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val attachmentRef = SecureHash.randomSHA256()
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
val e = assertFailsWith(MissingAttachmentsException::class) {
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withServiceHub(serviceHub)
.withAttachmentsClassLoader(listOf(attachmentRef))
serialized.deserialize(context = inboundContext)
}
assertEquals(attachmentRef, e.ids.single())
}
}
}

View File

@ -10,8 +10,8 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.AttachmentsClassLoaderTests
import net.corda.nodeapi.internal.AttachmentsClassLoader
import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Rule
import org.junit.Test
@ -82,11 +82,11 @@ class DefaultSerializableSerializer : Serializer<DefaultSerializable>() {
class CordaClassResolverTests {
val factory: SerializationFactory = object : SerializationFactory() {
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented")
}
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented")
}
}