From 6d375351bdfdc503a7e1b21bb17debf4792ae7b6 Mon Sep 17 00:00:00 2001
From: Rick Parker <rick.parker@r3.com>
Date: Tue, 28 Feb 2017 11:17:57 +0000
Subject: [PATCH] Add a header to all serialised data & switch to compatibility
 serializer. (#294)

Add a header to all serialised data & switch to compatibility serializer
---
 .../core/serialization/DefaultKryoCustomizer.kt    |  9 ++++++++-
 .../kotlin/net/corda/core/serialization/Kryo.kt    | 14 ++++++++++++--
 .../core/flows/BroadcastTransactionFlowTest.kt     |  6 ++----
 .../node/services/vault/schemas/VaultSchema.kt     |  3 +--
 .../src/main/resources/simulation/trade.json       |  4 ++--
 5 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt
index a7157a733d..b9ef3e23d2 100644
--- a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt
+++ b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt
@@ -1,6 +1,8 @@
 package net.corda.core.serialization
 
 import com.esotericsoftware.kryo.Kryo
+import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
+import com.esotericsoftware.kryo.serializers.FieldSerializer
 import com.esotericsoftware.kryo.util.MapReferenceResolver
 import de.javakaffee.kryoserializers.ArraysAsListSerializer
 import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
@@ -25,9 +27,14 @@ object DefaultKryoCustomizer {
         ServiceLoader.load(CordaPluginRegistry::class.java).toList().filter { it.customizeSerialization(customization) }
     }
 
-    // TODO: move all register() to addDefaultSerializer()
     fun customize(kryo: Kryo): Kryo {
         return kryo.apply {
+            // Store a little schema of field names in the stream the first time a class is used which increases tolerance
+            // for change to a class.
+            setDefaultSerializer(CompatibleFieldSerializer::class.java)
+            // Take the safest route here and allow subclasses to have fields named the same as super classes.
+            fieldSerializerConfig.setCachedFieldNameStrategy(FieldSerializer.CachedFieldNameStrategy.EXTENDED)
+
             // Allow construction of objects using a JVM backdoor that skips invoking the constructors, if there is no
             // no-arg constructor available.
             instantiatorStrategy = Kryo.DefaultInstantiatorStrategy(StdInstantiatorStrategy())
diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt
index 310d15f5c4..b86e8147da 100644
--- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt
+++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt
@@ -76,10 +76,19 @@ class SerializedBytes<T : Any>(bytes: ByteArray, val internalOnly: Boolean = fal
     fun writeToFile(path: Path): Path = Files.write(path, bytes)
 }
 
+// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB
+private val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray())
+
 // Some extension functions that make deserialisation convenient and provide auto-casting of the result.
 fun <T : Any> ByteArray.deserialize(kryo: Kryo = threadLocalP2PKryo()): T {
-    @Suppress("UNCHECKED_CAST")
-    return kryo.readClassAndObject(Input(this)) as T
+    Input(this).use {
+        val header = OpaqueBytes(it.readBytes(8))
+        if (header != KryoHeaderV0_1) {
+            throw KryoException("Serialized bytes header does not match any known format.")
+        }
+        @Suppress("UNCHECKED_CAST")
+        return kryo.readClassAndObject(it) as T
+    }
 }
 
 fun <T : Any> OpaqueBytes.deserialize(kryo: Kryo = threadLocalP2PKryo()): T {
@@ -114,6 +123,7 @@ object SerializedBytesSerializer : Serializer<SerializedBytes<Any>>() {
 fun <T : Any> T.serialize(kryo: Kryo = threadLocalP2PKryo(), internalOnly: Boolean = false): SerializedBytes<T> {
     val stream = ByteArrayOutputStream()
     Output(stream).use {
+        it.writeBytes(KryoHeaderV0_1.bytes)
         kryo.writeClassAndObject(it, this)
     }
     return SerializedBytes(stream.toByteArray(), internalOnly)
diff --git a/core/src/test/kotlin/net/corda/core/flows/BroadcastTransactionFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/BroadcastTransactionFlowTest.kt
index e6b8dbe758..da812439fe 100644
--- a/core/src/test/kotlin/net/corda/core/flows/BroadcastTransactionFlowTest.kt
+++ b/core/src/test/kotlin/net/corda/core/flows/BroadcastTransactionFlowTest.kt
@@ -1,6 +1,5 @@
 package net.corda.core.flows
 
-import com.esotericsoftware.kryo.io.Input
 import com.pholser.junit.quickcheck.From
 import com.pholser.junit.quickcheck.Property
 import com.pholser.junit.quickcheck.generator.GenerationStatus
@@ -8,7 +7,7 @@ import com.pholser.junit.quickcheck.generator.Generator
 import com.pholser.junit.quickcheck.random.SourceOfRandomness
 import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
 import net.corda.contracts.testing.SignedTransactionGenerator
-import net.corda.core.serialization.createKryo
+import net.corda.core.serialization.deserialize
 import net.corda.core.serialization.serialize
 import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
 import org.junit.runner.RunWith
@@ -25,9 +24,8 @@ class BroadcastTransactionFlowTest {
 
     @Property
     fun serialiseDeserialiseOfNotifyMessageWorks(@From(NotifyTxRequestMessageGenerator::class) message: NotifyTxRequest) {
-        val kryo = createKryo()
         val serialized = message.serialize().bytes
-        val deserialized = kryo.readClassAndObject(Input(serialized))
+        val deserialized = serialized.deserialize<NotifyTxRequest>()
         assertEquals(deserialized, message)
     }
 }
diff --git a/node-schemas/src/main/kotlin/net/corda/node/services/vault/schemas/VaultSchema.kt b/node-schemas/src/main/kotlin/net/corda/node/services/vault/schemas/VaultSchema.kt
index 3c0c566581..e1a0dafff5 100644
--- a/node-schemas/src/main/kotlin/net/corda/node/services/vault/schemas/VaultSchema.kt
+++ b/node-schemas/src/main/kotlin/net/corda/node/services/vault/schemas/VaultSchema.kt
@@ -3,7 +3,6 @@ package net.corda.node.services.vault.schemas
 import io.requery.*
 import net.corda.core.node.services.Vault
 import net.corda.core.schemas.requery.Requery
-import net.corda.core.schemas.requery.converters.InstantConverter
 import java.time.Instant
 
 object VaultSchema {
@@ -50,7 +49,7 @@ object VaultSchema {
 
         /** refers to serialized transaction Contract State */
         // TODO: define contract state size maximum size and adjust length accordingly
-        @get:Column(name = "contract_state", length = 10000)
+        @get:Column(name = "contract_state", length = 100000)
         var contractState: ByteArray
 
         /** state lifecycle: unconsumed, consumed */
diff --git a/samples/irs-demo/src/main/resources/simulation/trade.json b/samples/irs-demo/src/main/resources/simulation/trade.json
index 9416828185..a7630d5c85 100644
--- a/samples/irs-demo/src/main/resources/simulation/trade.json
+++ b/samples/irs-demo/src/main/resources/simulation/trade.json
@@ -1,6 +1,6 @@
 {
   "fixedLeg": {
-    "fixedRatePayer": "2eFzn8gRQq7nNgypMCjKik4w8i565TM3xBmp85eefhG1c24VSj5",
+    "fixedRatePayer": "3ThWzJauCq7qLrcX4KuKHxKnxZ6HoxnxU7pFL1HwfCkCJLUfTJ9zN92oxRLxnw",
     "notional": {
       "quantity": 2500000000,
       "token": "USD"
@@ -25,7 +25,7 @@
     "interestPeriodAdjustment": "Adjusted"
   },
   "floatingLeg": {
-    "floatingRatePayer": "2eFzn8gJj7xcdBxExC7XEiiX36dw6HfG3MCpjMt2CaejwUnfAxb",
+    "floatingRatePayer": "3ThWzJauCq7qLrcX4KndaoA3TXWtoLzUSmQ9ia8xVWxHTmVGjXXXFXm9R9Wh2T",
     "notional": {
       "quantity": 2500000000,
       "token": "USD"