CORDA-1383: Cleaned up the JSON format of WireTransaction and SignedTransaction (#3248)

In particular correctly outputs the custom state and command objects in the txs. Also fixed up deserialisation back into the transaction objects.
This commit is contained in:
Shams Asari 2018-05-30 16:37:41 +01:00 committed by GitHub
parent ed70fea3a7
commit 7b09795795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 569 additions and 174 deletions

2
.idea/compiler.xml generated
View File

@ -71,6 +71,8 @@
<module name="example-code_integrationTest" target="1.8" />
<module name="example-code_main" target="1.8" />
<module name="example-code_test" target="1.8" />
<module name="experimental-blobinspector_main" target="1.8" />
<module name="experimental-blobinspector_test" target="1.8" />
<module name="experimental-kryo-hook_main" target="1.8" />
<module name="experimental-kryo-hook_test" target="1.8" />
<module name="experimental_main" target="1.8" />

View File

@ -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'

View File

@ -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<T>(private val objectInstance: T) : JsonDeserializer<T>() {
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)

View File

@ -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<BeanPropertyWriter>): MutableList<BeanPropertyWriter> {
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<NetworkHostAndPort>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
return NetworkHostAndPort.parse(parser.text)
}
}
private class NetworkHostAndPortDeserializer : SimpleDeserializer<NetworkHostAndPort>({ 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<PartyAndCertificate
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
val mapper = gen.codec as JacksonSupport.PartyObjectMapper
if (mapper.isFullParties) {
gen.writeObject(PartyAndCertificateWrapper(value.name, value.certPath))
gen.writeObject(PartyAndCertificateJson(value.name, value.certPath))
} else {
gen.writeObject(value.party)
}
}
}
private class PartyAndCertificateWrapper(val name: CordaX500Name, val certPath: CertPath)
private class PartyAndCertificateJson(val name: CordaX500Name, val certPath: CertPath)
@JsonSerialize(using = SignedTransactionSerializer::class)
@JsonDeserialize(using = SignedTransactionDeserializer::class)
@ -117,18 +133,226 @@ private interface SignedTransactionMixin
private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
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<SignedTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
val wrapper = parser.readValueAs<SignedTransactionWrapper>()
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
val wrapper = parser.readValueAs<StxJson>()
val core = wrapper.run { wire ?: filtered ?: notaryChangeWire ?: contractUpgradeWire ?: contractUpgradeFiltered!! }
return SignedTransaction(core, wrapper.signatures)
}
}
private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List<TransactionSignature>)
@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<TransactionSignature>
) {
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<WireTransaction>() {
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<WireTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): WireTransaction {
val wrapper = parser.readValueAs<WireTransactionJson>()
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<StateRef>,
val outputs: List<TransactionState<*>>,
val commands: List<Command<*>>,
val timeWindow: TimeWindow?,
val attachments: List<SecureHash>,
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<TimeWindow>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): TimeWindow {
return parser.readValueAs<TimeWindowJson>().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<PrivacySalt>() {
override fun serialize(value: PrivacySalt, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.bytes.toHexString())
}
}
private class PrivacySaltDeserializer : SimpleDeserializer<PrivacySalt>({ 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<SignatureMetadata>() {
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<SignatureMetadata>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignatureMetadata {
val json = parser.readValueAsTree<ObjectNode>()
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<PartialTree>() {
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<PartialTree>() {
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<SignatureScheme>() {
override fun serialize(value: SignatureScheme, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.schemeCodeName)
}
}
private class SignatureSchemeDeserializer : JsonDeserializer<SignatureScheme>() {
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)

View File

@ -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 <reified T> JsonParser.readValueAs(): T = readValueAs(T::class.java)
inline fun <reified T : Any> JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this)
inline fun <reified T : Any> JsonNode.childrenAs(mapper: ObjectMapper): List<T> {
return elements().asSequence().map { it.valueAs<T>(mapper) }.toList()
}
@JacksonAnnotationsInside
@JsonSerialize(using = ToStringSerializer::class)
annotation class ToStringSerialize
abstract class SimpleDeserializer<T>(private val func: JsonParser.() -> T) : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): T = func(parser)
}

View File

@ -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<ObjectNode>(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<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
assertThat(signatureMetadata.valueAs<SignatureMetadata>(mapper)).isEqualTo(metadata)
assertThat(partialMerkleTree.isNull).isTrue()
assertThat(bytesJson.binaryValue()).isEqualTo(transactionSignature.bytes)
assertThat(byJson.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
assertThat(signatureMetadataJson.valueAs<SignatureMetadata>(mapper)).isEqualTo(signatureMetadata)
assertThat(partialMerkleTreeJson.valueAs<PartialMerkleTree>(mapper).root).isEqualTo(partialMerkleTree.root)
assertThat(mapper.convertValue<TransactionSignature>(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<ObjectNode>(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<TransactionSignature>(mapper) }.toList()
assertThat(sigs).isEqualTo(stx.sigs)
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs)
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "privacySalt")
assertThat(wtxFields[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs)
assertThat(wtxFields[3].childrenAs<SecureHash>(mapper)).isEqualTo(wtx.attachments)
assertThat(wtxFields[4].childrenAs<TransactionState<*>>(mapper)).isEqualTo(wtx.outputs)
assertThat(wtxFields[5].childrenAs<Command<*>>(mapper)).isEqualTo(wtx.commands)
assertThat(wtxFields[6].valueAs<TimeWindow>(mapper)).isEqualTo(wtx.timeWindow)
assertThat(wtxFields[7].valueAs<PrivacySalt>(mapper)).isEqualTo(wtx.privacySalt)
assertThat(mapper.convertValue<WireTransaction>(wtxJson)).isEqualTo(wtx)
assertThat(mapper.convertValue<SignedTransaction>(json)).isEqualTo(stx)
}
@Test
fun TransactionState() {
val txState = createTransactionState()
val json = mapper.valueToTree<ObjectNode>(txState)
println(mapper.writeValueAsString(json))
partyObjectMapper.identities += listOf(MINI_CORP.party, DUMMY_NOTARY)
assertThat(mapper.convertValue<TransactionState<*>>(json)).isEqualTo(txState)
}
@Test
fun Command() {
val command = Command(DummyCommandData, listOf(BOB_PUBKEY))
val json = mapper.valueToTree<ObjectNode>(command)
assertThat(mapper.convertValue<Command<*>>(json)).isEqualTo(command)
}
@Test
fun `TimeWindow - fromOnly`() {
val fromOnly = TimeWindow.fromOnly(Instant.now())
val json = mapper.valueToTree<ObjectNode>(fromOnly)
assertThat(mapper.convertValue<TimeWindow>(json)).isEqualTo(fromOnly)
}
@Test
fun `TimeWindow - untilOnly`() {
val untilOnly = TimeWindow.untilOnly(Instant.now())
val json = mapper.valueToTree<ObjectNode>(untilOnly)
assertThat(mapper.convertValue<TimeWindow>(json)).isEqualTo(untilOnly)
}
@Test
fun `TimeWindow - between`() {
val between = TimeWindow.between(Instant.now(), Instant.now() + 1.days)
val json = mapper.valueToTree<ObjectNode>(between)
assertThat(mapper.convertValue<TimeWindow>(json)).isEqualTo(between)
}
@Test
fun PrivacySalt() {
val privacySalt = net.corda.core.contracts.PrivacySalt()
val json = mapper.valueToTree<TextNode>(privacySalt)
assertThat(json.textValue()).isEqualTo(privacySalt.bytes.toHexString())
assertThat(mapper.convertValue<PrivacySalt>(json)).isEqualTo(privacySalt)
}
@Test
fun SignatureMetadata() {
val signatureMetadata = SignatureMetadata(2, Crypto.ECDSA_SECP256R1_SHA256.schemeNumberID)
val json = mapper.valueToTree<ObjectNode>(signatureMetadata)
val (platformVersion, scheme) = json.assertHasOnlyFields("platformVersion", "scheme")
assertThat(platformVersion.intValue()).isEqualTo(2)
assertThat(scheme.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256")
assertThat(mapper.convertValue<SignatureMetadata>(json)).isEqualTo(signatureMetadata)
}
@Test
fun `SignatureMetadata on unknown schemeNumberID`() {
val signatureMetadata = SignatureMetadata(2, Int.MAX_VALUE)
val json = mapper.valueToTree<ObjectNode>(signatureMetadata)
assertThat(json["scheme"].intValue()).isEqualTo(Int.MAX_VALUE)
assertThat(mapper.convertValue<SignatureMetadata>(json)).isEqualTo(signatureMetadata)
}
@Test
fun `SignatureScheme serialization`() {
val json = mapper.valueToTree<TextNode>(Crypto.ECDSA_SECP256R1_SHA256)
assertThat(json.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256")
}
@Test
fun `SignatureScheme deserialization`() {
assertThat(mapper.convertValue<SignatureScheme>(TextNode("EDDSA_ED25519_SHA512"))).isSameAs(Crypto.EDDSA_ED25519_SHA512)
assertThat(mapper.convertValue<SignatureScheme>(IntNode(4))).isSameAs(Crypto.EDDSA_ED25519_SHA512)
}
@Test
fun `PartialTree IncludedLeaf`() {
val includedLeaf = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
val json = mapper.valueToTree<ObjectNode>(includedLeaf)
assertThat(json.assertHasOnlyFields("includedLeaf")[0].textValue()).isEqualTo(includedLeaf.hash.toString())
assertThat(mapper.convertValue<PartialTree.IncludedLeaf>(json)).isEqualTo(includedLeaf)
}
@Test
fun `PartialTree Leaf`() {
val leaf = PartialTree.Leaf(SecureHash.randomSHA256())
val json = mapper.valueToTree<ObjectNode>(leaf)
assertThat(json.assertHasOnlyFields("leaf")[0].textValue()).isEqualTo(leaf.hash.toString())
assertThat(mapper.convertValue<PartialTree.Leaf>(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<ObjectNode>(node)
println(mapper.writeValueAsString(json))
val (leftJson, rightJson) = json.assertHasOnlyFields("left", "right")
assertThat(leftJson.valueAs<PartialTree>(mapper)).isEqualTo(node.left)
assertThat(rightJson.valueAs<PartialTree>(mapper)).isEqualTo(node.right)
assertThat(mapper.convertValue<PartialTree.Node>(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<ObjectNode>(node)
println(mapper.writeValueAsString(json))
assertThat(mapper.convertValue<PartialTree.Node>(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<NonCtorPropertiesData>(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<ObjectNode>(KotlinObject)
assertThat(mapper.convertValue<KotlinObject>(json)).isSameAs(KotlinObject)
}
@Test
fun `@CordaSerializable kotlin object`() {
val json = mapper.valueToTree<ObjectNode>(CordaSerializableKotlinObject)
assertThat(mapper.convertValue<CordaSerializableKotlinObject>(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<DummyContract.SingleOwnerState> {
return TransactionState(
data = DummyContract.SingleOwnerState(magicNumber = 123, owner = MINI_CORP.party),
contract = DummyContract.PROGRAM_ID,
notary = DUMMY_NOTARY
)
}
private inline fun <reified T : Any> 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<Party>()

View File

@ -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].

View File

@ -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.

View File

@ -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 <T : Any> KClass<T>.objectOrNewInstance(): T {
return this.objectInstance ?: this.createInstance()
}
/** Similar to [KClass.objectInstance] but also works on private objects. */
val <T : Any> Class<T>.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()

View File

@ -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<Int>()
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<IllegalArgumentException> { 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<IllegalArgumentException> { collection.noneOrSingle { it > 0 } }
}
@ -39,7 +40,7 @@ class InternalUtilsTest {
val collection = listOf(1, 2, 1)
assertFailsWith<IllegalArgumentException> { collection.noneOrSingle() }
assertFailsWith<IllegalArgumentException> { 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<String?>::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
}

View File

@ -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``

View File

@ -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<String, Any> {
val values = HashMap<String, Any>()
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) {

View File

@ -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

View File

@ -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<FieldFocus> {
val foci = ArrayList<FieldFocus>()
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) {

View File

@ -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)

View File

@ -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<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
// 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
}
}
}
}

View File

@ -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)