Add a header to all serialised data & switch to compatibility serializer. (#294)

Add a header to all serialised data & switch to compatibility serializer
This commit is contained in:
Rick Parker 2017-02-28 11:17:57 +00:00 committed by GitHub
parent 6b4950290e
commit 6d375351bd
5 changed files with 25 additions and 11 deletions

View File

@ -1,6 +1,8 @@
package net.corda.core.serialization package net.corda.core.serialization
import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.MapReferenceResolver import com.esotericsoftware.kryo.util.MapReferenceResolver
import de.javakaffee.kryoserializers.ArraysAsListSerializer import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
@ -25,9 +27,14 @@ object DefaultKryoCustomizer {
ServiceLoader.load(CordaPluginRegistry::class.java).toList().filter { it.customizeSerialization(customization) } ServiceLoader.load(CordaPluginRegistry::class.java).toList().filter { it.customizeSerialization(customization) }
} }
// TODO: move all register() to addDefaultSerializer()
fun customize(kryo: Kryo): Kryo { fun customize(kryo: Kryo): Kryo {
return kryo.apply { 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 // Allow construction of objects using a JVM backdoor that skips invoking the constructors, if there is no
// no-arg constructor available. // no-arg constructor available.
instantiatorStrategy = Kryo.DefaultInstantiatorStrategy(StdInstantiatorStrategy()) instantiatorStrategy = Kryo.DefaultInstantiatorStrategy(StdInstantiatorStrategy())

View File

@ -76,10 +76,19 @@ class SerializedBytes<T : Any>(bytes: ByteArray, val internalOnly: Boolean = fal
fun writeToFile(path: Path): Path = Files.write(path, bytes) 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. // Some extension functions that make deserialisation convenient and provide auto-casting of the result.
fun <T : Any> ByteArray.deserialize(kryo: Kryo = threadLocalP2PKryo()): T { fun <T : Any> ByteArray.deserialize(kryo: Kryo = threadLocalP2PKryo()): T {
@Suppress("UNCHECKED_CAST") Input(this).use {
return kryo.readClassAndObject(Input(this)) as T 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 { 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> { fun <T : Any> T.serialize(kryo: Kryo = threadLocalP2PKryo(), internalOnly: Boolean = false): SerializedBytes<T> {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
Output(stream).use { Output(stream).use {
it.writeBytes(KryoHeaderV0_1.bytes)
kryo.writeClassAndObject(it, this) kryo.writeClassAndObject(it, this)
} }
return SerializedBytes(stream.toByteArray(), internalOnly) return SerializedBytes(stream.toByteArray(), internalOnly)

View File

@ -1,6 +1,5 @@
package net.corda.core.flows package net.corda.core.flows
import com.esotericsoftware.kryo.io.Input
import com.pholser.junit.quickcheck.From import com.pholser.junit.quickcheck.From
import com.pholser.junit.quickcheck.Property import com.pholser.junit.quickcheck.Property
import com.pholser.junit.quickcheck.generator.GenerationStatus 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.random.SourceOfRandomness
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
import net.corda.contracts.testing.SignedTransactionGenerator 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.core.serialization.serialize
import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -25,9 +24,8 @@ class BroadcastTransactionFlowTest {
@Property @Property
fun serialiseDeserialiseOfNotifyMessageWorks(@From(NotifyTxRequestMessageGenerator::class) message: NotifyTxRequest) { fun serialiseDeserialiseOfNotifyMessageWorks(@From(NotifyTxRequestMessageGenerator::class) message: NotifyTxRequest) {
val kryo = createKryo()
val serialized = message.serialize().bytes val serialized = message.serialize().bytes
val deserialized = kryo.readClassAndObject(Input(serialized)) val deserialized = serialized.deserialize<NotifyTxRequest>()
assertEquals(deserialized, message) assertEquals(deserialized, message)
} }
} }

View File

@ -3,7 +3,6 @@ package net.corda.node.services.vault.schemas
import io.requery.* import io.requery.*
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.schemas.requery.Requery import net.corda.core.schemas.requery.Requery
import net.corda.core.schemas.requery.converters.InstantConverter
import java.time.Instant import java.time.Instant
object VaultSchema { object VaultSchema {
@ -50,7 +49,7 @@ object VaultSchema {
/** refers to serialized transaction Contract State */ /** refers to serialized transaction Contract State */
// TODO: define contract state size maximum size and adjust length accordingly // 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 var contractState: ByteArray
/** state lifecycle: unconsumed, consumed */ /** state lifecycle: unconsumed, consumed */

View File

@ -1,6 +1,6 @@
{ {
"fixedLeg": { "fixedLeg": {
"fixedRatePayer": "2eFzn8gRQq7nNgypMCjKik4w8i565TM3xBmp85eefhG1c24VSj5", "fixedRatePayer": "3ThWzJauCq7qLrcX4KuKHxKnxZ6HoxnxU7pFL1HwfCkCJLUfTJ9zN92oxRLxnw",
"notional": { "notional": {
"quantity": 2500000000, "quantity": 2500000000,
"token": "USD" "token": "USD"
@ -25,7 +25,7 @@
"interestPeriodAdjustment": "Adjusted" "interestPeriodAdjustment": "Adjusted"
}, },
"floatingLeg": { "floatingLeg": {
"floatingRatePayer": "2eFzn8gJj7xcdBxExC7XEiiX36dw6HfG3MCpjMt2CaejwUnfAxb", "floatingRatePayer": "3ThWzJauCq7qLrcX4KndaoA3TXWtoLzUSmQ9ia8xVWxHTmVGjXXXFXm9R9Wh2T",
"notional": { "notional": {
"quantity": 2500000000, "quantity": 2500000000,
"token": "USD" "token": "USD"