CORDA-946 - Correct backward compatibility issue with fingerprinting

Backport from Corda master
This commit is contained in:
Katelyn Baker 2018-02-05 16:01:45 +00:00
parent 7ca83eba0b
commit b32b6b6d29
5 changed files with 101 additions and 12 deletions

View File

@ -177,11 +177,23 @@ class EvolutionSerializerGetter : EvolutionSerializerGetterBase() {
override fun getEvolutionSerializer(factory: SerializerFactory,
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> =
factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) {
schemas: SerializationSchemas): AMQPSerializer<Any> {
return factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) {
when (typeNotation) {
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory)
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas)
is RestrictedType -> {
// The fingerprint of a generic collection can be changed through bug fixes to the
// fingerprinting function making it appear as if the class has altered whereas it hasn't.
// Given we don't support the evolution of these generic containers, if it appears
// one has been changed, simply return the original serializer and associate it with
// both the new and old fingerprint
if (newSerializer is CollectionSerializer || newSerializer is MapSerializer) {
newSerializer
} else {
EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas)
}
}
}
}
}
}

View File

@ -460,6 +460,6 @@ private fun fingerprintForObject(
.putUnencodedChars(prop.getter.name)
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+4) }
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+1) }
return hasher
}

View File

@ -120,16 +120,17 @@ val typeStrToType: Map<Pair<String, Boolean>, Class<out Any?>> = mapOf(
Pair("byte", false) to Byte::class.javaObjectType
)
fun String.stripGenerics() : String = if(this.endsWith('>')) {
this.substring(0, this.indexOf('<'))
} else this
fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type, mandatory)] ?: when (type) {
"string" -> String::class.java
"binary" -> ByteArray::class.java
"*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0])
else -> {
classloader.loadClass(
if (type.endsWith("?>")) {
type.substring(0, type.indexOf('<'))
} else type)
"*" -> if (requires.isEmpty()) Any::class.java else {
classloader.loadClass(requires[0].stripGenerics())
}
else -> classloader.loadClass(type.stripGenerics())
}
fun AMQPField.validateType(classloader: ClassLoader) = when (type) {

View File

@ -1,12 +1,21 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.TestIdentity
import org.junit.Ignore
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.net.URI
import java.time.Instant
import kotlin.test.assertEquals
// To regenerate any of the binary test files do the following
@ -462,4 +471,71 @@ class EvolvabilityTests {
assertEquals(f, db3.f)
assertEquals(-1, db3.g)
}
//
// This test uses a NetworkParameters signed set of bytes generated by R3 Corda and
// is here to ensure we can still read them. This test exists because of the break in
// being able to deserialize an object serialized prior to some fixes to the fingerprinter.
//
// The file itself was generated from R3 Corda at commit
// 6a6b6f256 Skip cache invalidation during init() - caches are still null.
//
// To regenerate the file un-ignore the test below this one (regenerate broken network parameters),
// to regenerate at a specific version add that test to a checkout at the desired sha then take
// the resulting file and add to the repo, changing the filename as appropriate
//
@Test
fun readBrokenNetworkParameters(){
val sf = testDefaultFactory()
sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf))
sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
//
// filename breakdown
// networkParams - because this is a serialised set of network parameters
// r3corda - generated by R3 Corda instead of Corda
// 6a6b6f256 - Commit sha of the build that generated the file we're testing against
//
val resource = "networkParams.r3corda.6a6b6f256"
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<SignedData<NetworkParameters>>(sc2))
val networkParams = DeserializationInput(sf).deserialize(deserializedC.raw)
assertEquals(1000, networkParams.maxMessageSize)
assertEquals(1000, networkParams.maxTransactionSize)
assertEquals(3, networkParams.minimumPlatformVersion)
assertEquals(1, networkParams.notaries.size)
assertEquals(TestIdentity(DUMMY_NOTARY_NAME, 20).party, networkParams.notaries.firstOrNull()?.identity)
}
//
// This test created a serialized and signed set of Network Parameters to test whether we
// can still deserialize them
//
@Test
@Ignore("This test simply regenerates the test file used for readBrokenNetworkParameters")
fun `regenerate broken network parameters`() {
// note: 6a6b6f256 is the sha that generates the file
val resource = "networkParams.<corda version>.<commit sha>"
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val networkParameters = NetworkParameters(
3, listOf(NotaryInfo(DUMMY_NOTARY, false)),1000, 1000, Instant.EPOCH, 1 )
val sf = testDefaultFactory()
sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf))
sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
val testOutput = TestSerializationOutput(true, sf)
val serialized = testOutput.serialize(networkParameters)
val keyPair = generateKeyPair()
val sig = keyPair.private.sign(serialized.bytes, keyPair.public)
val signed = SignedData(serialized, sig)
val signedAndSerialized = testOutput.serialize(signed)
File(URI("$localPath/$resource")).writeBytes( signedAndSerialized.bytes)
}
}