mirror of
https://github.com/corda/corda.git
synced 2024-12-22 14:22:28 +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,
|
override fun getEvolutionSerializer(factory: SerializerFactory,
|
||||||
typeNotation: TypeNotation,
|
typeNotation: TypeNotation,
|
||||||
newSerializer: AMQPSerializer<Any>,
|
newSerializer: AMQPSerializer<Any>,
|
||||||
schemas: SerializationSchemas): AMQPSerializer<Any> =
|
schemas: SerializationSchemas): AMQPSerializer<Any> {
|
||||||
factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) {
|
return factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) {
|
||||||
when (typeNotation) {
|
when (typeNotation) {
|
||||||
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory)
|
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(prop.getter.name)
|
||||||
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
.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
|
return hasher
|
||||||
}
|
}
|
||||||
|
@ -120,16 +120,17 @@ val typeStrToType: Map<Pair<String, Boolean>, Class<out Any?>> = mapOf(
|
|||||||
Pair("byte", false) to Byte::class.javaObjectType
|
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) {
|
fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type, mandatory)] ?: when (type) {
|
||||||
"string" -> String::class.java
|
"string" -> String::class.java
|
||||||
"binary" -> ByteArray::class.java
|
"binary" -> ByteArray::class.java
|
||||||
"*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0])
|
"*" -> if (requires.isEmpty()) Any::class.java else {
|
||||||
else -> {
|
classloader.loadClass(requires[0].stripGenerics())
|
||||||
classloader.loadClass(
|
|
||||||
if (type.endsWith("?>")) {
|
|
||||||
type.substring(0, type.indexOf('<'))
|
|
||||||
} else type)
|
|
||||||
}
|
}
|
||||||
|
else -> classloader.loadClass(type.stripGenerics())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AMQPField.validateType(classloader: ClassLoader) = when (type) {
|
fun AMQPField.validateType(classloader: ClassLoader) = when (type) {
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
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.DeprecatedConstructorForDeserialization
|
||||||
import net.corda.core.serialization.SerializedBytes
|
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.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 org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.time.Instant
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
// To regenerate any of the binary test files do the following
|
// To regenerate any of the binary test files do the following
|
||||||
@ -462,4 +471,71 @@ class EvolvabilityTests {
|
|||||||
assertEquals(f, db3.f)
|
assertEquals(f, db3.f)
|
||||||
assertEquals(-1, db3.g)
|
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