mirror of
https://github.com/corda/corda.git
synced 2024-12-22 06:17:55 +00:00
CORDA-946 - Correct backward compatibility issue with fingerprinting
Backport from Corda master
This commit is contained in:
parent
7ca83eba0b
commit
b32b6b6d29
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user