mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Merge commit 'fe617818895edab334d80c5e8de2b38f39e67af6' into chrisr3-os44-merge
This commit is contained in:
commit
1ef62870bb
@ -0,0 +1,14 @@
|
||||
package net.corda.contracts.serialization.generics
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
@CordaSerializable
|
||||
data class DataObject(val value: Long) : Comparable<DataObject> {
|
||||
override fun toString(): String {
|
||||
return "$value data points"
|
||||
}
|
||||
|
||||
override fun compareTo(other: DataObject): Int {
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.corda.contracts.serialization.generics
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import java.util.Optional
|
||||
|
||||
@Suppress("unused")
|
||||
class GenericTypeContract : Contract {
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
val state = tx.outputsOfType<State>()
|
||||
require(state.isNotEmpty()) {
|
||||
"Requires at least one data state"
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
|
||||
class State(val owner: AbstractParty, val data: DataObject) : ContractState {
|
||||
override val participants: List<AbstractParty> = listOf(owner)
|
||||
|
||||
@Override
|
||||
override fun toString(): String {
|
||||
return data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The [price] field is the important feature of the [Purchase]
|
||||
* class because its type is [Optional] with a CorDapp-specific
|
||||
* generic type parameter. It does not matter that the [price]
|
||||
* is not used; it only matters that the [Purchase] command
|
||||
* must be serialized as part of building a new transaction.
|
||||
*/
|
||||
class Purchase(val price: Optional<DataObject>) : CommandData
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.corda.flows.serialization.generics
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.serialization.generics.DataObject
|
||||
import net.corda.contracts.serialization.generics.GenericTypeContract.Purchase
|
||||
import net.corda.contracts.serialization.generics.GenericTypeContract.State
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.util.Optional
|
||||
|
||||
@StartableByRPC
|
||||
class GenericTypeFlow(private val purchase: DataObject) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
override fun call(): SecureHash {
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
val stx = serviceHub.signInitialTransaction(
|
||||
TransactionBuilder(notary)
|
||||
.addOutputState(State(ourIdentity, purchase))
|
||||
.addCommand(Command(Purchase(Optional.of(purchase)), ourIdentity.owningKey))
|
||||
)
|
||||
stx.verify(serviceHub, checkSufficientSignatures = false)
|
||||
return stx.id
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.contracts.serialization.generics.DataObject
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.serialization.generics.GenericTypeFlow
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.junit.Test
|
||||
|
||||
@Suppress("FunctionName")
|
||||
class ContractWithGenericTypeTest {
|
||||
companion object {
|
||||
const val DATA_VALUE = 5000L
|
||||
|
||||
@JvmField
|
||||
val logger = loggerFor<ContractWithGenericTypeTest>()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `flow with generic type`() {
|
||||
val user = User("u", "p", setOf(Permissions.all()))
|
||||
driver(DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.serialization.generics").signed(),
|
||||
cordappWithPackages("net.corda.contracts.serialization.generics").signed()
|
||||
)
|
||||
)) {
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val txID = CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::GenericTypeFlow, DataObject(DATA_VALUE))
|
||||
.returnValue
|
||||
.getOrThrow()
|
||||
}
|
||||
logger.info("TX-ID=$txID")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.contracts.serialization.generics.DataObject
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.serialization.generics.GenericTypeFlow
|
||||
import net.corda.node.DeterministicSourcesRule
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
|
||||
@Suppress("FunctionName")
|
||||
class DeterministicContractWithGenericTypeTest {
|
||||
companion object {
|
||||
const val DATA_VALUE = 5000L
|
||||
|
||||
@JvmField
|
||||
val logger = loggerFor<DeterministicContractWithGenericTypeTest>()
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test DJVM can deserialise command with generic type`() {
|
||||
val user = User("u", "p", setOf(Permissions.all()))
|
||||
driver(DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.serialization.generics").signed(),
|
||||
cordappWithPackages("net.corda.contracts.serialization.generics").signed()
|
||||
),
|
||||
djvmBootstrapSource = djvmSources.bootstrap,
|
||||
djvmCordaSource = djvmSources.corda
|
||||
)) {
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val txID = CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::GenericTypeFlow, DataObject(DATA_VALUE))
|
||||
.returnValue
|
||||
.getOrThrow()
|
||||
}
|
||||
logger.info("TX-ID=$txID")
|
||||
}
|
||||
}
|
||||
}
|
@ -75,7 +75,7 @@ class SandboxSerializerFactoryFactory(
|
||||
)
|
||||
)
|
||||
|
||||
val fingerPrinter = TypeModellingFingerPrinter(customSerializerRegistry)
|
||||
val fingerPrinter = TypeModellingFingerPrinter(customSerializerRegistry, classLoader)
|
||||
|
||||
val localSerializerFactory = DefaultLocalSerializerFactory(
|
||||
whitelist = context.whitelist,
|
||||
|
@ -7,7 +7,6 @@ import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.serialization.internal.model.*
|
||||
import net.corda.serialization.internal.model.TypeIdentifier.*
|
||||
import net.corda.serialization.internal.model.TypeIdentifier.Companion.classLoaderFor
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
@ -161,7 +160,7 @@ class DefaultLocalSerializerFactory(
|
||||
val declaredGenericType = if (declaredType !is ParameterizedType
|
||||
&& localTypeInformation.typeIdentifier is Parameterised
|
||||
&& declaredClass != Class::class.java) {
|
||||
localTypeInformation.typeIdentifier.getLocalType(classLoaderFor(declaredClass))
|
||||
localTypeInformation.typeIdentifier.getLocalType(classloader)
|
||||
} else {
|
||||
declaredType
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ object SerializerFactoryBuilder {
|
||||
val localTypeModel = ConfigurableLocalTypeModel(typeModelConfiguration)
|
||||
|
||||
val fingerPrinter = overrideFingerPrinter ?:
|
||||
TypeModellingFingerPrinter(customSerializerRegistry)
|
||||
TypeModellingFingerPrinter(customSerializerRegistry, classCarpenter.classloader)
|
||||
|
||||
val localSerializerFactory = DefaultLocalSerializerFactory(
|
||||
whitelist,
|
||||
|
@ -45,12 +45,12 @@ sealed class TypeIdentifier {
|
||||
* Obtain a nicely-formatted representation of the identified type, for help with debugging.
|
||||
*/
|
||||
fun prettyPrint(simplifyClassNames: Boolean = true): String = when(this) {
|
||||
is TypeIdentifier.UnknownType -> "?"
|
||||
is TypeIdentifier.TopType -> "*"
|
||||
is TypeIdentifier.Unparameterised -> name.simplifyClassNameIfRequired(simplifyClassNames)
|
||||
is TypeIdentifier.Erased -> "${name.simplifyClassNameIfRequired(simplifyClassNames)} (erased)"
|
||||
is TypeIdentifier.ArrayOf -> "${componentType.prettyPrint(simplifyClassNames)}[]"
|
||||
is TypeIdentifier.Parameterised ->
|
||||
is UnknownType -> "?"
|
||||
is TopType -> "*"
|
||||
is Unparameterised -> name.simplifyClassNameIfRequired(simplifyClassNames)
|
||||
is Erased -> "${name.simplifyClassNameIfRequired(simplifyClassNames)} (erased)"
|
||||
is ArrayOf -> "${componentType.prettyPrint(simplifyClassNames)}[]"
|
||||
is Parameterised ->
|
||||
name.simplifyClassNameIfRequired(simplifyClassNames) + parameters.joinToString(", ", "<", ">") {
|
||||
it.prettyPrint(simplifyClassNames)
|
||||
}
|
||||
@ -63,8 +63,6 @@ sealed class TypeIdentifier {
|
||||
// This method has locking. So we memo the value here.
|
||||
private val systemClassLoader: ClassLoader = ClassLoader.getSystemClassLoader()
|
||||
|
||||
fun classLoaderFor(clazz: Class<*>): ClassLoader = clazz.classLoader ?: systemClassLoader
|
||||
|
||||
/**
|
||||
* Obtain the [TypeIdentifier] for an erased Java class.
|
||||
*
|
||||
@ -81,7 +79,7 @@ sealed class TypeIdentifier {
|
||||
* Obtain the [TypeIdentifier] for a Java [Type] (typically obtained by calling one of
|
||||
* [java.lang.reflect.Parameter.getAnnotatedType],
|
||||
* [java.lang.reflect.Field.getGenericType] or
|
||||
* [java.lang.reflect.Method.getGenericReturnType]). Wildcard types and type variables are converted to [Unknown].
|
||||
* [java.lang.reflect.Method.getGenericReturnType]). Wildcard types and type variables are converted to [UnknownType].
|
||||
*
|
||||
* @param type The [Type] to obtain a [TypeIdentifier] for.
|
||||
* @param resolutionContext Optionally, a [Type] which can be used to resolve type variables, for example a
|
||||
@ -273,5 +271,5 @@ private class ReconstitutedParameterizedType(
|
||||
other.ownerType == ownerType &&
|
||||
Arrays.equals(other.actualTypeArguments, actualTypeArguments)
|
||||
override fun hashCode(): Int =
|
||||
Arrays.hashCode(actualTypeArguments) xor Objects.hashCode(ownerType) xor Objects.hashCode(rawType)
|
||||
actualTypeArguments.contentHashCode() xor Objects.hashCode(ownerType) xor Objects.hashCode(rawType)
|
||||
}
|
@ -5,7 +5,6 @@ import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.toBase64
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import net.corda.serialization.internal.model.TypeIdentifier.*
|
||||
import net.corda.serialization.internal.model.TypeIdentifier.Companion.classLoaderFor
|
||||
import java.lang.reflect.ParameterizedType
|
||||
|
||||
/**
|
||||
@ -31,6 +30,7 @@ interface FingerPrinter {
|
||||
*/
|
||||
class TypeModellingFingerPrinter(
|
||||
private val customTypeDescriptorLookup: CustomSerializerRegistry,
|
||||
private val classLoader: ClassLoader,
|
||||
private val debugEnabled: Boolean = false) : FingerPrinter {
|
||||
|
||||
private val cache: MutableMap<TypeIdentifier, String> = DefaultCacheProvider.createCache()
|
||||
@ -42,7 +42,7 @@ class TypeModellingFingerPrinter(
|
||||
* the Fingerprinter cannot guarantee that.
|
||||
*/
|
||||
cache.getOrPut(typeInformation.typeIdentifier) {
|
||||
FingerPrintingState(customTypeDescriptorLookup, FingerprintWriter(debugEnabled))
|
||||
FingerPrintingState(customTypeDescriptorLookup, classLoader, FingerprintWriter(debugEnabled))
|
||||
.fingerprint(typeInformation)
|
||||
}
|
||||
}
|
||||
@ -95,6 +95,7 @@ internal class FingerprintWriter(debugEnabled: Boolean = false) {
|
||||
*/
|
||||
private class FingerPrintingState(
|
||||
private val customSerializerRegistry: CustomSerializerRegistry,
|
||||
private val classLoader: ClassLoader,
|
||||
private val writer: FingerprintWriter) {
|
||||
|
||||
companion object {
|
||||
@ -200,7 +201,7 @@ private class FingerPrintingState(
|
||||
private fun fingerprintName(type: LocalTypeInformation) {
|
||||
val identifier = type.typeIdentifier
|
||||
when (identifier) {
|
||||
is TypeIdentifier.ArrayOf -> writer.write(identifier.componentType.name).writeArray()
|
||||
is ArrayOf -> writer.write(identifier.componentType.name).writeArray()
|
||||
else -> writer.write(identifier.name)
|
||||
}
|
||||
}
|
||||
@ -239,7 +240,7 @@ private class FingerPrintingState(
|
||||
val observedGenericType = if (observedType !is ParameterizedType
|
||||
&& type.typeIdentifier is Parameterised
|
||||
&& observedClass != Class::class.java) {
|
||||
type.typeIdentifier.getLocalType(classLoaderFor(observedClass))
|
||||
type.typeIdentifier.getLocalType(classLoader)
|
||||
} else {
|
||||
observedType
|
||||
}
|
||||
@ -259,6 +260,5 @@ private class FingerPrintingState(
|
||||
// and deserializing (assuming deserialization is occurring in a factory that didn't
|
||||
// serialise the object in the first place (and thus the cache lookup fails). This is also
|
||||
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
|
||||
private fun hasSeen(type: TypeIdentifier) = (type in typesSeen)
|
||||
&& (type != TypeIdentifier.UnknownType)
|
||||
private fun hasSeen(type: TypeIdentifier) = (type in typesSeen) && (type != UnknownType)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class TypeModellingFingerPrinterTests {
|
||||
|
||||
val descriptorBasedSerializerRegistry = DefaultDescriptorBasedSerializerRegistry()
|
||||
val customRegistry = CachingCustomSerializerRegistry(descriptorBasedSerializerRegistry)
|
||||
val fingerprinter = TypeModellingFingerPrinter(customRegistry, true)
|
||||
val fingerprinter = TypeModellingFingerPrinter(customRegistry, ClassLoader.getSystemClassLoader(), true)
|
||||
|
||||
// See https://r3-cev.atlassian.net/browse/CORDA-2266
|
||||
@Test(timeout=300_000)
|
||||
|
Loading…
Reference in New Issue
Block a user