diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 9632eef3de..5cd3aa02f4 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -71,6 +71,8 @@ + + diff --git a/build.gradle b/build.gradle index ea49fc62ba..36c4bfc2ad 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ buildscript { * https://issues.apache.org/jira/browse/ARTEMIS-1559 */ ext.artemis_version = '2.5.0' - ext.jackson_version = '2.9.3' + ext.jackson_version = '2.9.5' ext.jetty_version = '9.4.7.v20170914' ext.jersey_version = '2.25' ext.assertj_version = '3.8.0' diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt index af64d56aa4..2820a7d15f 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.* import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier import com.fasterxml.jackson.databind.deser.std.NumberDeserializers import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule @@ -22,9 +23,7 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.crypto.* import net.corda.core.identity.* -import net.corda.core.internal.CertRole -import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.uncheckedCast +import net.corda.core.internal.* import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.services.IdentityService @@ -37,7 +36,6 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.parsePublicKeyBase58 import net.corda.core.utilities.toBase58String import org.bouncycastle.asn1.x509.KeyPurposeId -import java.lang.reflect.Modifier import java.math.BigDecimal import java.nio.charset.StandardCharsets.UTF_8 import java.security.PublicKey @@ -169,7 +167,9 @@ object JacksonSupport { addSerializer(Date::class.java, DateSerializer) }) registerModule(CordaModule()) - registerModule(KotlinModule()) + registerModule(KotlinModule().apply { + setDeserializerModifier(KotlinObjectDeserializerModifier) + }) addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java) addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java) @@ -178,6 +178,19 @@ object JacksonSupport { } } + private object KotlinObjectDeserializerModifier : BeanDeserializerModifier() { + override fun modifyDeserializer(config: DeserializationConfig, + beanDesc: BeanDescription, + deserializer: JsonDeserializer<*>): JsonDeserializer<*> { + val objectInstance = beanDesc.beanClass.kotlinObjectInstance + return if (objectInstance != null) KotlinObjectDeserializer(objectInstance) else deserializer + } + } + + private class KotlinObjectDeserializer(private val objectInstance: T) : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): T = objectInstance + } + @ToStringSerialize @JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class) private interface BigDecimalMixin @@ -210,7 +223,7 @@ object JacksonSupport { val keyPurposeIds = KeyPurposeId::class.java .fields - .filter { Modifier.isStatic(it.modifiers) && it.type == KeyPurposeId::class.java } + .filter { it.isStatic && it.type == KeyPurposeId::class.java } .associateBy({ (it.get(null) as KeyPurposeId).id }, { it.name }) val knownExtensions = setOf( @@ -235,7 +248,7 @@ object JacksonSupport { writeObjectField("issuerUniqueID", value.issuerUniqueID) writeObjectField("subjectUniqueID", value.subjectUniqueID) writeObjectField("keyUsage", value.keyUsage?.asList()?.mapIndexedNotNull { i, flag -> if (flag) keyUsages[i] else null }) - writeObjectField("extendedKeyUsage", value.extendedKeyUsage.map { keyPurposeIds.getOrDefault(it, it) }) + writeObjectField("extendedKeyUsage", value.extendedKeyUsage.map { keyPurposeIds[it] ?: it }) jsonObject("basicConstraints") { val isCa = value.basicConstraints != -1 writeBooleanField("isCA", isCa) diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt index 5dd3fe395a..32ca33befe 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt @@ -2,30 +2,39 @@ package net.corda.client.jackson.internal +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonInclude.Include +import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParseException import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonToken import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.ser.BeanPropertyWriter import com.fasterxml.jackson.databind.ser.BeanSerializerModifier +import com.google.common.primitives.Booleans import net.corda.client.jackson.JacksonSupport -import net.corda.core.contracts.Amount -import net.corda.core.crypto.DigitalSignature -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.TransactionSignature +import net.corda.core.contracts.* +import net.corda.core.crypto.* +import net.corda.core.crypto.PartialMerkleTree.PartialTree import net.corda.core.identity.* import net.corda.core.internal.DigitalSignatureWithCert +import net.corda.core.internal.kotlinObjectInstance import net.corda.core.node.NodeInfo -import net.corda.core.serialization.* -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.WireTransaction +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.transactions.* import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.parseAsHex +import net.corda.core.utilities.toHexString import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.constructorForDeserialization @@ -33,6 +42,7 @@ import net.corda.serialization.internal.amqp.hasCordaSerializable import net.corda.serialization.internal.amqp.propertiesForSerialization import java.security.PublicKey import java.security.cert.CertPath +import java.time.Instant class CordaModule : SimpleModule("corda-core") { override fun setupModule(context: SetupContext) { @@ -50,12 +60,20 @@ class CordaModule : SimpleModule("corda-core") { context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java) context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java) context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java) + context.setMixInAnnotations(SecureHash::class.java, SecureHashSHA256Mixin::class.java) context.setMixInAnnotations(SerializedBytes::class.java, SerializedBytesMixin::class.java) context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(SignedTransaction::class.java, SignedTransactionMixin::class.java) - context.setMixInAnnotations(WireTransaction::class.java, JacksonSupport.WireTransactionMixin::class.java) + context.setMixInAnnotations(WireTransaction::class.java, WireTransactionMixin::class.java) + context.setMixInAnnotations(TransactionState::class.java, TransactionStateMixin::class.java) + context.setMixInAnnotations(Command::class.java, CommandMixin::class.java) + context.setMixInAnnotations(TimeWindow::class.java, TimeWindowMixin::class.java) + context.setMixInAnnotations(PrivacySalt::class.java, PrivacySaltMixin::class.java) + context.setMixInAnnotations(SignatureScheme::class.java, SignatureSchemeMixin::class.java) + context.setMixInAnnotations(SignatureMetadata::class.java, SignatureMetadataMixin::class.java) + context.setMixInAnnotations(PartialTree::class.java, PartialTreeMixin::class.java) context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java) } } @@ -64,21 +82,23 @@ class CordaModule : SimpleModule("corda-core") { * Use the same properties that AMQP serialization uses if the POJO is @CordaSerializable */ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() { - // We need a SerializerFactory when scanning for properties but don't actually use it so any will do - private val serializerFactory = SerializerFactory(AllWhitelist, Thread.currentThread().contextClassLoader) + // We need to pass in a SerializerFactory when scanning for properties, but don't actually do any serialisation so any will do. + private val serializerFactory = SerializerFactory(AllWhitelist, javaClass.classLoader) override fun changeProperties(config: SerializationConfig, beanDesc: BeanDescription, beanProperties: MutableList): MutableList { - if (hasCordaSerializable(beanDesc.beanClass)) { - val ctor = constructorForDeserialization(beanDesc.beanClass) - val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory) + val beanClass = beanDesc.beanClass + if (hasCordaSerializable(beanClass) && beanClass.kotlinObjectInstance == null) { + val ctor = constructorForDeserialization(beanClass) + val amqpProperties = propertiesForSerialization(ctor, beanClass, serializerFactory) .serializationOrder .map { it.serializer.name } - beanProperties.removeIf { it.name !in amqpProperties } - (amqpProperties - beanProperties.map { it.name }).let { + val propertyRenames = beanDesc.findProperties().associateBy({ it.name }, { it.internalName }) + (amqpProperties - propertyRenames.values).let { check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" } } + beanProperties.removeIf { propertyRenames[it.name] !in amqpProperties } } return beanProperties } @@ -88,11 +108,7 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() @JsonDeserialize(using = NetworkHostAndPortDeserializer::class) private interface NetworkHostAndPortMixin -private class NetworkHostAndPortDeserializer : JsonDeserializer() { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort { - return NetworkHostAndPort.parse(parser.text) - } -} +private class NetworkHostAndPortDeserializer : SimpleDeserializer({ NetworkHostAndPort.parse(text) }) @JsonSerialize(using = PartyAndCertificateSerializer::class) // TODO Add deserialization which follows the same lookup logic as Party @@ -102,14 +118,14 @@ private class PartyAndCertificateSerializer : JsonSerializer() { override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) { - gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs)) + val core = value.coreTransaction + val stxJson = when (core) { + is WireTransaction -> StxJson(wire = core, signatures = value.sigs) + is FilteredTransaction -> StxJson(filtered = core, signatures = value.sigs) + is NotaryChangeWireTransaction -> StxJson(notaryChangeWire = core, signatures = value.sigs) + is ContractUpgradeWireTransaction -> StxJson(contractUpgradeWire = core, signatures = value.sigs) + is ContractUpgradeFilteredTransaction -> StxJson(contractUpgradeFiltered = core, signatures = value.sigs) + else -> throw IllegalArgumentException("Don't know about ${core.javaClass}") + } + gen.writeObject(stxJson) } } private class SignedTransactionDeserializer : JsonDeserializer() { override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction { - val wrapper = parser.readValueAs() - return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures) + val wrapper = parser.readValueAs() + val core = wrapper.run { wire ?: filtered ?: notaryChangeWire ?: contractUpgradeWire ?: contractUpgradeFiltered!! } + return SignedTransaction(core, wrapper.signatures) } } -private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List) +@JsonInclude(Include.NON_NULL) +private data class StxJson( + val wire: WireTransaction? = null, + val filtered: FilteredTransaction? = null, + val notaryChangeWire: NotaryChangeWireTransaction? = null, + val contractUpgradeWire: ContractUpgradeWireTransaction? = null, + val contractUpgradeFiltered: ContractUpgradeFilteredTransaction? = null, + val signatures: List +) { + init { + val count = Booleans.countTrue(wire != null, filtered != null, notaryChangeWire != null, contractUpgradeWire != null, contractUpgradeFiltered != null) + require(count == 1) { this } + } +} + +@JsonSerialize(using = WireTransactionSerializer::class) +@JsonDeserialize(using = WireTransactionDeserializer::class) +private interface WireTransactionMixin + +private class WireTransactionSerializer : JsonSerializer() { + override fun serialize(value: WireTransaction, gen: JsonGenerator, serializers: SerializerProvider) { + gen.writeObject(WireTransactionJson( + value.id, + value.notary, + value.inputs, + value.outputs, + value.commands, + value.timeWindow, + value.attachments, + value.privacySalt + )) + } +} + +private class WireTransactionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): WireTransaction { + val wrapper = parser.readValueAs() + val componentGroups = WireTransaction.createComponentGroups( + wrapper.inputs, + wrapper.outputs, + wrapper.commands, + wrapper.attachments, + wrapper.notary, + wrapper.timeWindow + ) + return WireTransaction(componentGroups, wrapper.privacySalt) + } +} + +private class WireTransactionJson(val id: SecureHash, + val notary: Party?, + val inputs: List, + val outputs: List>, + val commands: List>, + val timeWindow: TimeWindow?, + val attachments: List, + val privacySalt: PrivacySalt) + +private interface TransactionStateMixin { + @get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) + val data: ContractState + @get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) + val constraint: AttachmentConstraint +} + +private interface CommandMixin { + @get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) + val value: CommandData +} + +@JsonDeserialize(using = TimeWindowDeserializer::class) +private interface TimeWindowMixin + +private class TimeWindowDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): TimeWindow { + return parser.readValueAs().run { + when { + fromTime != null && untilTime != null -> TimeWindow.between(fromTime, untilTime) + fromTime != null -> TimeWindow.fromOnly(fromTime) + untilTime != null -> TimeWindow.untilOnly(untilTime) + else -> throw JsonParseException(parser, "Neither fromTime nor untilTime exists for TimeWindow") + } + } + } +} + +private data class TimeWindowJson(val fromTime: Instant?, val untilTime: Instant?) + +@JsonSerialize(using = PrivacySaltSerializer::class) +@JsonDeserialize(using = PrivacySaltDeserializer::class) +private interface PrivacySaltMixin + +private class PrivacySaltSerializer : JsonSerializer() { + override fun serialize(value: PrivacySalt, gen: JsonGenerator, serializers: SerializerProvider) { + gen.writeString(value.bytes.toHexString()) + } +} + +private class PrivacySaltDeserializer : SimpleDeserializer({ PrivacySalt(text.parseAsHex()) }) + +// TODO Add a lookup function by number ID in Crypto +private val signatureSchemesByNumberID = Crypto.supportedSignatureSchemes().associateBy { it.schemeNumberID } + +@JsonSerialize(using = SignatureMetadataSerializer::class) +@JsonDeserialize(using = SignatureMetadataDeserializer::class) +private interface SignatureMetadataMixin + +private class SignatureMetadataSerializer : JsonSerializer() { + override fun serialize(value: SignatureMetadata, gen: JsonGenerator, serializers: SerializerProvider) { + gen.jsonObject { + writeNumberField("platformVersion", value.platformVersion) + writeObjectField("scheme", value.schemeNumberID.let { signatureSchemesByNumberID[it] ?: it }) + } + } +} + +private class SignatureMetadataDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignatureMetadata { + val json = parser.readValueAsTree() + val scheme = json["scheme"] + val schemeNumberID = if (scheme is IntNode) { + scheme.intValue() + } else { + Crypto.findSignatureScheme(scheme.textValue()).schemeNumberID + } + return SignatureMetadata(json["platformVersion"].intValue(), schemeNumberID) + } +} + +@JsonSerialize(using = PartialTreeSerializer::class) +@JsonDeserialize(using = PartialTreeDeserializer::class) +private interface PartialTreeMixin + +private class PartialTreeSerializer : JsonSerializer() { + override fun serialize(value: PartialTree, gen: JsonGenerator, serializers: SerializerProvider) { + gen.writeObject(convert(value)) + } + + private fun convert(tree: PartialTree): PartialTreeJson { + return when (tree) { + is PartialTree.IncludedLeaf -> PartialTreeJson(includedLeaf = tree.hash) + is PartialTree.Leaf -> PartialTreeJson(leaf = tree.hash) + is PartialTree.Node -> PartialTreeJson(left = convert(tree.left), right = convert(tree.right)) + else -> throw IllegalArgumentException("Don't know how to serialize $tree") + } + } +} + +private class PartialTreeDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): PartialTree { + return convert(parser.readValueAs(PartialTreeJson::class.java)) + } + + private fun convert(wrapper: PartialTreeJson): PartialTree { + return wrapper.run { + when { + includedLeaf != null -> PartialTree.IncludedLeaf(includedLeaf) + leaf != null -> PartialTree.Leaf(leaf) + else -> PartialTree.Node(convert(left!!), convert(right!!)) + } + } + } +} + +@JsonInclude(Include.NON_NULL) +private class PartialTreeJson(val includedLeaf: SecureHash? = null, + val leaf: SecureHash? = null, + val left: PartialTreeJson? = null, + val right: PartialTreeJson? = null) { + init { + if (includedLeaf != null) { + require(leaf == null && left == null && right == null) + } else if (leaf != null) { + require(left == null && right == null) + } else { + require(left != null && right != null) + } + } +} + +@JsonSerialize(using = SignatureSchemeSerializer::class) +@JsonDeserialize(using = SignatureSchemeDeserializer::class) +private interface SignatureSchemeMixin + +private class SignatureSchemeSerializer : JsonSerializer() { + override fun serialize(value: SignatureScheme, gen: JsonGenerator, serializers: SerializerProvider) { + gen.writeString(value.schemeCodeName) + } +} + +private class SignatureSchemeDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignatureScheme { + return if (parser.currentToken == JsonToken.VALUE_NUMBER_INT) { + signatureSchemesByNumberID[parser.intValue] ?: throw JsonParseException(parser, "Unable to find SignatureScheme ${parser.text}") + } else { + Crypto.findSignatureScheme(parser.text) + } + } +} @JsonSerialize(using = SerializedBytesSerializer::class) @JsonDeserialize(using = SerializedBytesDeserializer::class) diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt index 948b18f956..255aa85855 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt @@ -3,6 +3,8 @@ package net.corda.client.jackson.internal import com.fasterxml.jackson.annotation.JacksonAnnotationsInside import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.annotation.JsonSerialize @@ -20,6 +22,14 @@ inline fun JsonParser.readValueAs(): T = readValueAs(T::class.java) inline fun JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this) +inline fun JsonNode.childrenAs(mapper: ObjectMapper): List { + return elements().asSequence().map { it.valueAs(mapper) }.toList() +} + @JacksonAnnotationsInside @JsonSerialize(using = ToStringSerializer::class) -annotation class ToStringSerialize \ No newline at end of file +annotation class ToStringSerialize + +abstract class SimpleDeserializer(private val func: JsonParser.() -> T) : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): T = func(parser) +} diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt index 86c50a4160..9a7c0a8dfb 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt @@ -3,17 +3,20 @@ package net.corda.client.jackson import com.fasterxml.jackson.core.JsonFactory import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.node.BinaryNode +import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.node.TextNode import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.module.kotlin.convertValue import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever +import net.corda.client.jackson.internal.childrenAs import net.corda.client.jackson.internal.valueAs -import net.corda.core.contracts.Amount +import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey +import net.corda.core.crypto.PartialMerkleTree.PartialTree import net.corda.core.identity.* import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NodeInfo @@ -21,7 +24,10 @@ import net.corda.core.node.ServiceHub import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.serialize +import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.* import net.corda.finance.USD import net.corda.nodeapi.internal.crypto.x509Certificates @@ -39,10 +45,11 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters import java.math.BigInteger -import java.nio.charset.StandardCharsets.* +import java.nio.charset.StandardCharsets.UTF_8 import java.security.PublicKey import java.security.cert.CertPath import java.security.cert.X509Certificate +import java.time.Instant import java.util.* import javax.security.auth.x500.X500Principal import kotlin.collections.ArrayList @@ -52,7 +59,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: private companion object { val SEED: BigInteger = BigInteger.valueOf(20170922L) val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey - val BOB_PUBKEY = TestIdentity(BOB_NAME, 70).publicKey + val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).publicKey val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) @@ -76,6 +83,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: services = rigorousMock() cordappProvider = rigorousMock() doReturn(cordappProvider).whenever(services).cordappProvider + doReturn(testNetworkParameters()).whenever(services).networkParameters } @Test @@ -182,43 +190,190 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: @Test fun TransactionSignature() { - val metadata = SignatureMetadata(1, 1) - val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, metadata) + val signatureMetadata = SignatureMetadata(1, 1) + val partialMerkleTree = PartialMerkleTree(PartialTree.Node( + left = PartialTree.Leaf(SecureHash.randomSHA256()), + right = PartialTree.IncludedLeaf(SecureHash.randomSHA256()) + )) + val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, signatureMetadata, partialMerkleTree) val json = mapper.valueToTree(transactionSignature) - val (bytes, by, signatureMetadata, partialMerkleTree) = json.assertHasOnlyFields( + val (bytesJson, byJson, signatureMetadataJson, partialMerkleTreeJson) = json.assertHasOnlyFields( "bytes", "by", "signatureMetadata", "partialMerkleTree" ) - assertThat(bytes.binaryValue()).isEqualTo(transactionSignature.bytes) - assertThat(by.valueAs(mapper)).isEqualTo(BOB_PUBKEY) - assertThat(signatureMetadata.valueAs(mapper)).isEqualTo(metadata) - assertThat(partialMerkleTree.isNull).isTrue() + assertThat(bytesJson.binaryValue()).isEqualTo(transactionSignature.bytes) + assertThat(byJson.valueAs(mapper)).isEqualTo(BOB_PUBKEY) + assertThat(signatureMetadataJson.valueAs(mapper)).isEqualTo(signatureMetadata) + assertThat(partialMerkleTreeJson.valueAs(mapper).root).isEqualTo(partialMerkleTree.root) assertThat(mapper.convertValue(json)).isEqualTo(transactionSignature) } - // TODO Add test for PartialMerkleTree - @Test - fun SignedTransaction() { - val attachmentRef = SecureHash.randomSHA256() - doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) - doReturn(testNetworkParameters()).whenever(services).networkParameters - - val stx = makeDummyStx() + fun `SignedTransaction (WireTransaction)`() { + val attachmentId = SecureHash.randomSHA256() + doReturn(attachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) + val wtx = TransactionBuilder( + notary = DUMMY_NOTARY, + inputs = mutableListOf(StateRef(SecureHash.randomSHA256(), 1)), + attachments = mutableListOf(attachmentId), + outputs = mutableListOf(createTransactionState()), + commands = mutableListOf(Command(DummyCommandData, listOf(BOB_PUBKEY))), + window = TimeWindow.fromStartAndDuration(Instant.now(), 1.hours), + privacySalt = net.corda.core.contracts.PrivacySalt() + ).toWireTransaction(services) + val stx = sign(wtx) + partyObjectMapper.identities += listOf(MINI_CORP.party, DUMMY_NOTARY) val json = mapper.valueToTree(stx) println(mapper.writeValueAsString(json)) - val (txBits, signatures) = json.assertHasOnlyFields("txBits", "signatures") - assertThat(txBits.binaryValue()).isEqualTo(stx.txBits.bytes) - val sigs = signatures.elements().asSequence().map { it.valueAs(mapper) }.toList() - assertThat(sigs).isEqualTo(stx.sigs) + val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures") + assertThat(signaturesJson.childrenAs(mapper)).isEqualTo(stx.sigs) + val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "privacySalt") + assertThat(wtxFields[0].valueAs(mapper)).isEqualTo(wtx.id) + assertThat(wtxFields[1].valueAs(mapper)).isEqualTo(wtx.notary) + assertThat(wtxFields[2].childrenAs(mapper)).isEqualTo(wtx.inputs) + assertThat(wtxFields[3].childrenAs(mapper)).isEqualTo(wtx.attachments) + assertThat(wtxFields[4].childrenAs>(mapper)).isEqualTo(wtx.outputs) + assertThat(wtxFields[5].childrenAs>(mapper)).isEqualTo(wtx.commands) + assertThat(wtxFields[6].valueAs(mapper)).isEqualTo(wtx.timeWindow) + assertThat(wtxFields[7].valueAs(mapper)).isEqualTo(wtx.privacySalt) + assertThat(mapper.convertValue(wtxJson)).isEqualTo(wtx) assertThat(mapper.convertValue(json)).isEqualTo(stx) } + @Test + fun TransactionState() { + val txState = createTransactionState() + val json = mapper.valueToTree(txState) + println(mapper.writeValueAsString(json)) + partyObjectMapper.identities += listOf(MINI_CORP.party, DUMMY_NOTARY) + assertThat(mapper.convertValue>(json)).isEqualTo(txState) + } + + @Test + fun Command() { + val command = Command(DummyCommandData, listOf(BOB_PUBKEY)) + val json = mapper.valueToTree(command) + assertThat(mapper.convertValue>(json)).isEqualTo(command) + } + + @Test + fun `TimeWindow - fromOnly`() { + val fromOnly = TimeWindow.fromOnly(Instant.now()) + val json = mapper.valueToTree(fromOnly) + assertThat(mapper.convertValue(json)).isEqualTo(fromOnly) + } + + @Test + fun `TimeWindow - untilOnly`() { + val untilOnly = TimeWindow.untilOnly(Instant.now()) + val json = mapper.valueToTree(untilOnly) + assertThat(mapper.convertValue(json)).isEqualTo(untilOnly) + } + + @Test + fun `TimeWindow - between`() { + val between = TimeWindow.between(Instant.now(), Instant.now() + 1.days) + val json = mapper.valueToTree(between) + assertThat(mapper.convertValue(json)).isEqualTo(between) + } + + @Test + fun PrivacySalt() { + val privacySalt = net.corda.core.contracts.PrivacySalt() + val json = mapper.valueToTree(privacySalt) + assertThat(json.textValue()).isEqualTo(privacySalt.bytes.toHexString()) + assertThat(mapper.convertValue(json)).isEqualTo(privacySalt) + } + + @Test + fun SignatureMetadata() { + val signatureMetadata = SignatureMetadata(2, Crypto.ECDSA_SECP256R1_SHA256.schemeNumberID) + val json = mapper.valueToTree(signatureMetadata) + val (platformVersion, scheme) = json.assertHasOnlyFields("platformVersion", "scheme") + assertThat(platformVersion.intValue()).isEqualTo(2) + assertThat(scheme.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256") + assertThat(mapper.convertValue(json)).isEqualTo(signatureMetadata) + } + + @Test + fun `SignatureMetadata on unknown schemeNumberID`() { + val signatureMetadata = SignatureMetadata(2, Int.MAX_VALUE) + val json = mapper.valueToTree(signatureMetadata) + assertThat(json["scheme"].intValue()).isEqualTo(Int.MAX_VALUE) + assertThat(mapper.convertValue(json)).isEqualTo(signatureMetadata) + } + + @Test + fun `SignatureScheme serialization`() { + val json = mapper.valueToTree(Crypto.ECDSA_SECP256R1_SHA256) + assertThat(json.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256") + } + + @Test + fun `SignatureScheme deserialization`() { + assertThat(mapper.convertValue(TextNode("EDDSA_ED25519_SHA512"))).isSameAs(Crypto.EDDSA_ED25519_SHA512) + assertThat(mapper.convertValue(IntNode(4))).isSameAs(Crypto.EDDSA_ED25519_SHA512) + } + + @Test + fun `PartialTree IncludedLeaf`() { + val includedLeaf = PartialTree.IncludedLeaf(SecureHash.randomSHA256()) + val json = mapper.valueToTree(includedLeaf) + assertThat(json.assertHasOnlyFields("includedLeaf")[0].textValue()).isEqualTo(includedLeaf.hash.toString()) + assertThat(mapper.convertValue(json)).isEqualTo(includedLeaf) + } + + @Test + fun `PartialTree Leaf`() { + val leaf = PartialTree.Leaf(SecureHash.randomSHA256()) + val json = mapper.valueToTree(leaf) + assertThat(json.assertHasOnlyFields("leaf")[0].textValue()).isEqualTo(leaf.hash.toString()) + assertThat(mapper.convertValue(json)).isEqualTo(leaf) + } + + @Test + fun `simple PartialTree Node`() { + val node = PartialTree.Node( + left = PartialTree.Leaf(SecureHash.randomSHA256()), + right = PartialTree.IncludedLeaf(SecureHash.randomSHA256()) + ) + val json = mapper.valueToTree(node) + println(mapper.writeValueAsString(json)) + val (leftJson, rightJson) = json.assertHasOnlyFields("left", "right") + assertThat(leftJson.valueAs(mapper)).isEqualTo(node.left) + assertThat(rightJson.valueAs(mapper)).isEqualTo(node.right) + assertThat(mapper.convertValue(json)).isEqualTo(node) + } + + @Test + fun `complex PartialTree Node`() { + val node = PartialTree.Node( + left = PartialTree.IncludedLeaf(SecureHash.randomSHA256()), + right = PartialTree.Node( + left = PartialTree.Leaf(SecureHash.randomSHA256()), + right = PartialTree.Leaf(SecureHash.randomSHA256()) + ) + ) + val json = mapper.valueToTree(node) + println(mapper.writeValueAsString(json)) + assertThat(mapper.convertValue(json)).isEqualTo(node) + } + + // TODO Issued + // TODO PartyAndReference + @Test fun CordaX500Name() { - testToStringSerialisation(CordaX500Name(commonName = "COMMON", organisationUnit = "ORG UNIT", organisation = "ORG", locality = "NYC", state = "NY", country = "US")) + testToStringSerialisation(CordaX500Name( + commonName = "COMMON", + organisationUnit = "ORG UNIT", + organisation = "ORG", + locality = "NYC", + state = "NY", + country = "US" + )) } @Test @@ -462,14 +617,36 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: assertThat(mapper.convertValue(json)).isEqualTo(data) } - private fun makeDummyStx(): SignedTransaction { - val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) - .toWireTransaction(services) + @Test + fun `kotlin object`() { + val json = mapper.valueToTree(KotlinObject) + assertThat(mapper.convertValue(json)).isSameAs(KotlinObject) + } + + @Test + fun `@CordaSerializable kotlin object`() { + val json = mapper.valueToTree(CordaSerializableKotlinObject) + assertThat(mapper.convertValue(json)).isSameAs(CordaSerializableKotlinObject) + } + + private fun sign(ctx: CoreTransaction): SignedTransaction { + val partialMerkleTree = PartialMerkleTree(PartialTree.Node( + left = PartialTree.Leaf(SecureHash.randomSHA256()), + right = PartialTree.IncludedLeaf(SecureHash.randomSHA256()) + )) val signatures = listOf( - TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID)), + TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID), partialMerkleTree), TransactionSignature(ByteArray(1), BOB_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(BOB_PUBKEY).schemeNumberID)) ) - return SignedTransaction(wtx, signatures) + return SignedTransaction(ctx, signatures) + } + + private fun createTransactionState(): TransactionState { + return TransactionState( + data = DummyContract.SingleOwnerState(magicNumber = 123, owner = MINI_CORP.party), + contract = DummyContract.PROGRAM_ID, + notary = DUMMY_NOTARY + ) } private inline fun testToStringSerialisation(value: T) { @@ -495,6 +672,11 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: val nonCtor: Int get() = value } + private object KotlinObject + + @CordaSerializable + private object CordaSerializableKotlinObject + private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper { override var isFullParties: Boolean = false val identities = ArrayList() diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index d396330cb6..43c7c0544e 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -220,7 +220,7 @@ object Crypto { } /** - * Factory pattern to retrieve the corresponding [SignatureScheme] based on the type of the [String] input. + * Factory pattern to retrieve the corresponding [SignatureScheme] based on [SignatureScheme.schemeCodeName]. * This function is usually called by key generators and verify signature functions. * In case the input is not a key in the supportedSignatureSchemes map, null will be returned. * @param schemeCodeName a [String] that should match a supported signature scheme code name (e.g. ECDSA_SECP256K1_SHA256), see [Crypto]. diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt index 0176397c2f..6c1f87ed68 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt @@ -6,8 +6,9 @@ import java.security.spec.AlgorithmParameterSpec /** * This class is used to define a digital signature scheme. - * @param schemeNumberID we assign a number ID for better efficiency on-wire serialisation. Please ensure uniqueness between schemes. - * @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512). + * @param schemeNumberID unique number ID for better efficiency on-wire serialisation. + * @param schemeCodeName unique code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, + * EDDSA_ED25519_SHA512, SPHINCS-256_SHA512). * @param signatureOID ASN.1 algorithm identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA) * @param alternativeOIDs ASN.1 algorithm identifiers for keys of the signature, where we want to map multiple keys to * the same signature scheme. diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 43338d4706..f5a2fdbae1 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -7,12 +7,7 @@ import com.google.common.hash.HashingInputStream import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappConfig import net.corda.core.cordapp.CordappContext -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.DigitalSignature -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.SignedData -import net.corda.core.crypto.sha256 -import net.corda.core.crypto.sign +import net.corda.core.crypto.* import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.SerializationContext @@ -37,6 +32,7 @@ import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.lang.reflect.Field +import java.lang.reflect.Member import java.lang.reflect.Modifier import java.math.BigDecimal import java.net.HttpURLConnection @@ -51,23 +47,11 @@ import java.nio.file.Paths import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey -import java.security.cert.CertPath -import java.security.cert.CertPathValidator -import java.security.cert.CertPathValidatorException -import java.security.cert.PKIXCertPathValidatorResult -import java.security.cert.PKIXParameters -import java.security.cert.TrustAnchor -import java.security.cert.X509Certificate +import java.security.cert.* import java.time.Duration import java.time.temporal.Temporal import java.util.* -import java.util.Spliterator.DISTINCT -import java.util.Spliterator.IMMUTABLE -import java.util.Spliterator.NONNULL -import java.util.Spliterator.ORDERED -import java.util.Spliterator.SIZED -import java.util.Spliterator.SORTED -import java.util.Spliterator.SUBSIZED +import java.util.Spliterator.* import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit import java.util.stream.IntStream @@ -311,6 +295,23 @@ fun KClass.objectOrNewInstance(): T { return this.objectInstance ?: this.createInstance() } +/** Similar to [KClass.objectInstance] but also works on private objects. */ +val Class.kotlinObjectInstance: T? get() { + return try { + kotlin.objectInstance + } catch (_: Throwable) { + val field = try { getDeclaredField("INSTANCE") } catch (_: NoSuchFieldException) { null } + field?.let { + if (it.type == this && it.isPublic && it.isStatic && it.isFinal) { + it.isAccessible = true + uncheckedCast(it.get(null)) + } else { + null + } + } + } +} + /** * A simple wrapper around a [Field] object providing type safe read and write access using [value], ignoring the field's * visibility. @@ -385,6 +386,12 @@ inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifie inline val Class<*>.isConcreteClass: Boolean get() = !isInterface && !isAbstractClass +inline val Member.isPublic: Boolean get() = Modifier.isPublic(modifiers) + +inline val Member.isStatic: Boolean get() = Modifier.isStatic(modifiers) + +inline val Member.isFinal: Boolean get() = Modifier.isFinal(modifiers) + fun URI.toPath(): Path = Paths.get(this) fun URL.toPath(): Path = toURI().toPath() diff --git a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt index 0a2fb69f26..21f89c34af 100644 --- a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt @@ -1,6 +1,7 @@ package net.corda.core.internal -import org.assertj.core.api.Assertions +import net.corda.core.contracts.TimeWindow +import org.assertj.core.api.Assertions.assertThat import org.junit.Assert.assertArrayEquals import org.junit.Test import java.util.stream.IntStream @@ -8,29 +9,29 @@ import java.util.stream.Stream import kotlin.test.assertEquals import kotlin.test.assertFailsWith -class InternalUtilsTest { +open class InternalUtilsTest { @Test fun `noneOrSingle on an empty collection`() { val collection = emptyList() - Assertions.assertThat(collection.noneOrSingle()).isNull() - Assertions.assertThat(collection.noneOrSingle { it == 1 }).isNull() + assertThat(collection.noneOrSingle()).isNull() + assertThat(collection.noneOrSingle { it == 1 }).isNull() } @Test fun `noneOrSingle on a singleton collection`() { val collection = listOf(1) - Assertions.assertThat(collection.noneOrSingle()).isEqualTo(1) - Assertions.assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1) - Assertions.assertThat(collection.noneOrSingle { it == 2 }).isNull() + assertThat(collection.noneOrSingle()).isEqualTo(1) + assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1) + assertThat(collection.noneOrSingle { it == 2 }).isNull() } @Test fun `noneOrSingle on a collection with two items`() { val collection = listOf(1, 2) assertFailsWith { collection.noneOrSingle() } - Assertions.assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1) - Assertions.assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2) - Assertions.assertThat(collection.noneOrSingle { it == 3 }).isNull() + assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1) + assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2) + assertThat(collection.noneOrSingle { it == 3 }).isNull() assertFailsWith { collection.noneOrSingle { it > 0 } } } @@ -39,7 +40,7 @@ class InternalUtilsTest { val collection = listOf(1, 2, 1) assertFailsWith { collection.noneOrSingle() } assertFailsWith { collection.noneOrSingle { it == 1 } } - Assertions.assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2) + assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2) } @Test @@ -88,4 +89,19 @@ class InternalUtilsTest { assertEquals(Array::class.java, b.javaClass) assertArrayEquals(arrayOf("one", "two", null), b) } + + @Test + fun kotlinObjectInstance() { + assertThat(PublicObject::class.java.kotlinObjectInstance).isSameAs(PublicObject) + assertThat(PrivateObject::class.java.kotlinObjectInstance).isSameAs(PrivateObject) + assertThat(ProtectedObject::class.java.kotlinObjectInstance).isSameAs(ProtectedObject) + assertThat(TimeWindow::class.java.kotlinObjectInstance).isNull() + assertThat(PrivateClass::class.java.kotlinObjectInstance).isNull() + } + + object PublicObject + private object PrivateObject + protected object ProtectedObject + + private class PrivateClass } diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index bce0ddeb4b..3f1ee9d428 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -51,7 +51,10 @@ Unreleased The encoded bytes are also serialised into the ``encoded`` field. This can be used to deserialise an ``X509Certificate`` back. * ``CertPath`` objects are serialised as a list of ``X509Certificate`` objects. - * ``SignedTransaction`` is serialised into its ``txBits`` and ``signatures`` and can be deserialised back + * ``WireTransaction`` now nicely outputs into its components: ``id``, ``notary``, ``inputs``, ``attachments``, ``outputs``, + ``commands``, ``timeWindow`` and ``privacySalt``. This can be deserialised back. + * ``SignedTransaction`` is serialised into ``wire`` (i.e. currently only ``WireTransaction`` tested) and ``signatures``, + and can be deserialised back. * ``fullParties`` boolean parameter added to ``JacksonSupport.createDefaultMapper`` and ``createNonRpcMapper``. If ``true`` then ``Party`` objects are serialised as JSON objects with the ``name`` and ``owningKey`` fields. For ``PartyAndCertificate`` diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index 7d511ac61c..f3b78acd4e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -2,13 +2,9 @@ package net.corda.nodeapi.internal.config -import com.typesafe.config.Config -import com.typesafe.config.ConfigException -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigUtil -import com.typesafe.config.ConfigValueFactory -import com.typesafe.config.ConfigValueType +import com.typesafe.config.* import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.isStatic import net.corda.core.internal.noneOrSingle import net.corda.core.internal.uncheckedCast import net.corda.core.utilities.NetworkHostAndPort @@ -16,7 +12,6 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import java.lang.reflect.Field import java.lang.reflect.InvocationTargetException -import java.lang.reflect.Modifier.isStatic import java.lang.reflect.ParameterizedType import java.net.Proxy import java.net.URL @@ -209,7 +204,7 @@ fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig( private fun Any.toConfigMap(): Map { val values = HashMap() for (field in javaClass.declaredFields) { - if (isStatic(field.modifiers) || field.isSynthetic) continue + if (field.isStatic || field.isSynthetic) continue field.isAccessible = true val value = field.get(this) ?: continue val configValue = if (value is String || value is Boolean || value is Number) { diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt index 58e8b61c06..e9ce3e4d56 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt @@ -6,6 +6,7 @@ import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.Util +import net.corda.core.internal.kotlinObjectInstance import net.corda.core.internal.writer import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationContext @@ -15,7 +16,6 @@ import net.corda.serialization.internal.MutableClassWhitelist import net.corda.serialization.internal.TransientClassWhiteList import net.corda.serialization.internal.amqp.hasCordaSerializable import java.io.PrintWriter -import java.lang.reflect.Modifier import java.lang.reflect.Modifier.isAbstract import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.Paths @@ -75,22 +75,7 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl override fun registerImplicit(type: Class<*>): Registration { val targetType = typeForSerializationOf(type) - // Is this a Kotlin object? We use our own reflection here rather than .kotlin.objectInstance because Kotlin - // reflection won't work for private objects, and can throw exceptions in other circumstances as well. - val objectInstance = try { - targetType.declaredFields.singleOrNull { - it.name == "INSTANCE" && - it.type == type && - Modifier.isStatic(it.modifiers) && - Modifier.isFinal(it.modifiers) && - Modifier.isPublic(it.modifiers) - }?.let { - it.isAccessible = true - type.cast(it.get(null)!!) - } - } catch (t: Throwable) { - null - } + val objectInstance = targetType.kotlinObjectInstance // We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent. val references = kryo.references diff --git a/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt b/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt index d26aaeca06..d0cd3fa79f 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt @@ -1,7 +1,7 @@ package net.corda.node.utilities +import net.corda.core.internal.isStatic import java.lang.reflect.Method -import java.lang.reflect.Modifier import java.lang.reflect.Type import java.time.Instant @@ -131,7 +131,7 @@ object ObjectDiffer { private fun getFieldFoci(obj: Any) : List { val foci = ArrayList() for (method in obj.javaClass.declaredMethods) { - if (Modifier.isStatic(method.modifiers)) { + if (method.isStatic) { continue } if (method.name.startsWith("get") && method.name.length > 3 && method.parameterCount == 0) { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt index 36c63ace7b..188ce947a2 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt @@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp import com.google.common.hash.Hasher import com.google.common.hash.Hashing +import net.corda.core.internal.kotlinObjectInstance import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase64 import java.io.NotSerializableException @@ -153,7 +154,7 @@ class SerializerFingerPrinter : FingerPrinter { }.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH) } else { hasher.fingerprintWithCustomSerializerOrElse(factory!!, type, type) { - if (type.objectInstance() != null) { + if (type.kotlinObjectInstance != null) { // TODO: name collision is too likely for kotlin objects, we need to introduce some reference // to the CorDapp but maybe reference to the JAR in the short term. hasher.putUnencodedChars(type.name) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt index 47b5387413..36bc35d147 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt @@ -3,6 +3,7 @@ package net.corda.serialization.internal.amqp import com.google.common.primitives.Primitives import com.google.common.reflect.TypeToken import net.corda.core.internal.isConcreteClass +import net.corda.core.internal.isPublic import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.CordaSerializable @@ -155,7 +156,7 @@ fun Class.propertyDescriptors(): Map { // In addition, only getters that take zero parameters and setters that take a single // parameter will be considered clazz!!.declaredMethods?.map { func -> - if (!Modifier.isPublic(func.modifiers)) return@map + if (!func.isPublic) return@map if (func.name == "getClass") return@map PropertyDescriptorsRegex.re.find(func.name)?.apply { @@ -537,49 +538,3 @@ fun hasCordaSerializable(type: Class<*>): Boolean { || type.interfaces.any(::hasCordaSerializable) || (type.superclass != null && hasCordaSerializable(type.superclass)) } - -/** - * By default use Kotlin reflection and grab the objectInstance. However, that doesn't play nicely with nested - * private objects. Even setting the accessibility override (setAccessible) still causes an - * [IllegalAccessException] when attempting to retrieve the value of the INSTANCE field. - * - * Whichever reference to the class Kotlin reflection uses, override (set from setAccessible) on that field - * isn't set even when it was explicitly set as accessible before calling into the kotlin reflection routines. - * - * For example - * - * clazz.getDeclaredField("INSTANCE")?.apply { - * isAccessible = true - * kotlin.objectInstance // This throws as the INSTANCE field isn't accessible - * } - * - * Therefore default back to good old java reflection and simply look for the INSTANCE field as we are never going - * to serialize a companion object. - * - * As such, if objectInstance fails access, revert to Java reflection and try that - */ -fun Class<*>.objectInstance(): Any? { - return try { - this.kotlin.objectInstance - } catch (e: IllegalAccessException) { - // Check it really is an object (i.e. it has no constructor) - if (constructors.isNotEmpty()) null - else { - try { - this.getDeclaredField("INSTANCE")?.let { field -> - // and must be marked as both static and final (>0 means they're set) - if (modifiers and Modifier.STATIC == 0 || modifiers and Modifier.FINAL == 0) null - else { - val accessibility = field.isAccessible - field.isAccessible = true - val obj = field.get(null) - field.isAccessible = accessibility - obj - } - } - } catch (e: NoSuchFieldException) { - null - } - } - } -} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index eb22bab382..560ddb998d 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp import com.google.common.primitives.Primitives import com.google.common.reflect.TypeResolver +import net.corda.core.internal.kotlinObjectInstance import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.ClassWhitelist import net.corda.core.utilities.debug @@ -329,7 +330,7 @@ open class SerializerFactory( if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this) else ArraySerializer.make(type, this) } else { - val singleton = clazz.objectInstance() + val singleton = clazz.kotlinObjectInstance if (singleton != null) { whitelist.requireWhitelisted(clazz) SingletonSerializer(clazz, singleton, this)