diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index 7f87d53764..ca3487ead8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -61,6 +61,7 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.BitSetSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this)) + register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(this)) } for (whitelistProvider in serializationWhitelists) factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray()) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt new file mode 100644 index 0000000000..2440114a4c --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt @@ -0,0 +1,32 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.ContractAttachment +import net.corda.core.contracts.ContractClassName +import net.corda.core.serialization.MissingAttachmentsException +import net.corda.nodeapi.internal.serialization.GeneratedAttachment +import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory + +/** + * A serializer for [ContractAttachment] that uses a proxy object to write out the full attachment eagerly. + * @param factory the serializerFactory + */ +class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(ContractAttachment::class.java, + ContractAttachmentProxy::class.java, factory) { + override fun toProxy(obj: ContractAttachment): ContractAttachmentProxy { + val bytes = try { + obj.attachment.open().readBytes() + } catch (e: Exception) { + throw MissingAttachmentsException(listOf(obj.id)) + } + return ContractAttachmentProxy(GeneratedAttachment(bytes), obj.contract) + } + + override fun fromProxy(proxy: ContractAttachmentProxy): ContractAttachment { + return ContractAttachment(proxy.attachment, proxy.contract) + } + + data class ContractAttachmentProxy(val attachment: Attachment, val contract: ContractClassName) +} \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index a68cfefaf1..ea93b0038f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -1,10 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.CordaRuntimeException -import net.corda.core.contracts.Contract -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty @@ -13,19 +9,26 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.LedgerTransaction import net.corda.client.rpc.RPCException +import net.corda.core.contracts.* +import net.corda.core.internal.AbstractAttachment +import net.corda.core.serialization.MissingAttachmentsException import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.EmptyWhitelist +import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive import net.corda.testing.BOB_IDENTITY import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_PUBKEY +import net.corda.testing.contracts.DummyContract import net.corda.testing.withTestSerialization import org.apache.activemq.artemis.api.core.SimpleString import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.EncoderImpl +import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertNotSame import org.junit.Assert.assertSame +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Ignore import org.junit.Test import java.io.ByteArrayInputStream @@ -993,4 +996,36 @@ class SerializationOutputTests { obj[Month.AUGUST] = Month.AUGUST.value serdes(obj) } + + @Test + fun `test contract attachment serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(factory)) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(factory2)) + + val obj = ContractAttachment(GeneratedAttachment("test".toByteArray()), DummyContract.PROGRAM_ID) + val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) + assertEquals(obj.id, obj2.attachment.id) + assertEquals(obj.contract, obj2.contract) + assertArrayEquals(obj.open().readBytes(), obj2.open().readBytes()) + } + + @Test + fun `test contract attachment throws if missing attachment`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(factory)) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(factory2)) + + val obj = ContractAttachment(object : AbstractAttachment({ throw Exception() }) { + override val id = SecureHash.zeroHash + }, DummyContract.PROGRAM_ID) + + assertThatThrownBy { + serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) + }.isInstanceOf(MissingAttachmentsException::class.java) + } } \ No newline at end of file