Merge branch 'merge-5d1cc0b' into os-merge-5d1cc0b

# Conflicts:
#	.idea/compiler.xml
#	node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt
#	node/src/main/kotlin/net/corda/node/services/statemachine/transitions/StateMachine.kt
This commit is contained in:
Shams Asari 2018-05-30 21:47:48 +01:00
commit 03fae9bd7d
23 changed files with 627 additions and 206 deletions

View File

@ -49,7 +49,7 @@ buildscript {
* https://issues.apache.org/jira/browse/ARTEMIS-1559 * https://issues.apache.org/jira/browse/ARTEMIS-1559
*/ */
ext.artemis_version = '2.5.0' 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.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25' ext.jersey_version = '2.25'
ext.assertj_version = '3.8.0' ext.assertj_version = '3.8.0'

View File

@ -16,6 +16,7 @@ import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize 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.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
@ -32,9 +33,7 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.identity.* import net.corda.core.identity.*
import net.corda.core.internal.CertRole import net.corda.core.internal.*
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
@ -47,7 +46,6 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.parsePublicKeyBase58 import net.corda.core.utilities.parsePublicKeyBase58
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.toBase58String
import org.bouncycastle.asn1.x509.KeyPurposeId import org.bouncycastle.asn1.x509.KeyPurposeId
import java.lang.reflect.Modifier
import java.math.BigDecimal import java.math.BigDecimal
import java.nio.charset.StandardCharsets.UTF_8 import java.nio.charset.StandardCharsets.UTF_8
import java.security.PublicKey import java.security.PublicKey
@ -179,7 +177,9 @@ object JacksonSupport {
addSerializer(Date::class.java, DateSerializer) addSerializer(Date::class.java, DateSerializer)
}) })
registerModule(CordaModule()) registerModule(CordaModule())
registerModule(KotlinModule()) registerModule(KotlinModule().apply {
setDeserializerModifier(KotlinObjectDeserializerModifier)
})
addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java) addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java)
addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java) addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java)
@ -188,6 +188,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 @ToStringSerialize
@JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class) @JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class)
private interface BigDecimalMixin private interface BigDecimalMixin
@ -220,7 +233,7 @@ object JacksonSupport {
val keyPurposeIds = KeyPurposeId::class.java val keyPurposeIds = KeyPurposeId::class.java
.fields .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 }) .associateBy({ (it.get(null) as KeyPurposeId).id }, { it.name })
val knownExtensions = setOf( val knownExtensions = setOf(
@ -245,7 +258,7 @@ object JacksonSupport {
writeObjectField("issuerUniqueID", value.issuerUniqueID) writeObjectField("issuerUniqueID", value.issuerUniqueID)
writeObjectField("subjectUniqueID", value.subjectUniqueID) writeObjectField("subjectUniqueID", value.subjectUniqueID)
writeObjectField("keyUsage", value.keyUsage?.asList()?.mapIndexedNotNull { i, flag -> if (flag) keyUsages[i] else null }) 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") { jsonObject("basicConstraints") {
val isCa = value.basicConstraints != -1 val isCa = value.basicConstraints != -1
writeBooleanField("isCA", isCa) writeBooleanField("isCA", isCa)

View File

@ -2,30 +2,39 @@
package net.corda.client.jackson.internal 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.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.module.SimpleModule 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.node.ObjectNode
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter import com.fasterxml.jackson.databind.ser.BeanPropertyWriter
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import com.google.common.primitives.Booleans
import net.corda.client.jackson.JacksonSupport import net.corda.client.jackson.JacksonSupport
import net.corda.core.contracts.Amount import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.* import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.* import net.corda.core.serialization.SerializedBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.serialization.deserialize
import net.corda.core.transactions.WireTransaction import net.corda.core.serialization.serialize
import net.corda.core.transactions.*
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.NetworkHostAndPort 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.AllWhitelist
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.constructorForDeserialization 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 net.corda.serialization.internal.amqp.propertiesForSerialization
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.time.Instant
class CordaModule : SimpleModule("corda-core") { class CordaModule : SimpleModule("corda-core") {
override fun setupModule(context: SetupContext) { override fun setupModule(context: SetupContext) {
@ -50,12 +60,20 @@ class CordaModule : SimpleModule("corda-core") {
context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java) context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java)
context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java) context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java)
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::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(SerializedBytes::class.java, SerializedBytesMixin::class.java)
context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(SignedTransaction::class.java, SignedTransactionMixin::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) 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 * Use the same properties that AMQP serialization uses if the POJO is @CordaSerializable
*/ */
private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() { private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() {
// We need a SerializerFactory when scanning for properties but don't actually use it so any will do // 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, Thread.currentThread().contextClassLoader) private val serializerFactory = SerializerFactory(AllWhitelist, javaClass.classLoader)
override fun changeProperties(config: SerializationConfig, override fun changeProperties(config: SerializationConfig,
beanDesc: BeanDescription, beanDesc: BeanDescription,
beanProperties: MutableList<BeanPropertyWriter>): MutableList<BeanPropertyWriter> { beanProperties: MutableList<BeanPropertyWriter>): MutableList<BeanPropertyWriter> {
if (hasCordaSerializable(beanDesc.beanClass)) { val beanClass = beanDesc.beanClass
val ctor = constructorForDeserialization(beanDesc.beanClass) if (hasCordaSerializable(beanClass) && beanClass.kotlinObjectInstance == null) {
val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory) val ctor = constructorForDeserialization(beanClass)
val amqpProperties = propertiesForSerialization(ctor, beanClass, serializerFactory)
.serializationOrder .serializationOrder
.map { it.serializer.name } .map { it.serializer.name }
beanProperties.removeIf { it.name !in amqpProperties } val propertyRenames = beanDesc.findProperties().associateBy({ it.name }, { it.internalName })
(amqpProperties - beanProperties.map { it.name }).let { (amqpProperties - propertyRenames.values).let {
check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" } check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" }
} }
beanProperties.removeIf { propertyRenames[it.name] !in amqpProperties }
} }
return beanProperties return beanProperties
} }
@ -88,11 +108,7 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier()
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class) @JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
private interface NetworkHostAndPortMixin private interface NetworkHostAndPortMixin
private class NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() { private class NetworkHostAndPortDeserializer : SimpleDeserializer<NetworkHostAndPort>({ NetworkHostAndPort.parse(text) })
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
return NetworkHostAndPort.parse(parser.text)
}
}
@JsonSerialize(using = PartyAndCertificateSerializer::class) @JsonSerialize(using = PartyAndCertificateSerializer::class)
// TODO Add deserialization which follows the same lookup logic as Party // 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) { override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
val mapper = gen.codec as JacksonSupport.PartyObjectMapper val mapper = gen.codec as JacksonSupport.PartyObjectMapper
if (mapper.isFullParties) { if (mapper.isFullParties) {
gen.writeObject(PartyAndCertificateWrapper(value.name, value.certPath)) gen.writeObject(PartyAndCertificateJson(value.name, value.certPath))
} else { } else {
gen.writeObject(value.party) 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) @JsonSerialize(using = SignedTransactionSerializer::class)
@JsonDeserialize(using = SignedTransactionDeserializer::class) @JsonDeserialize(using = SignedTransactionDeserializer::class)
@ -117,18 +133,226 @@ private interface SignedTransactionMixin
private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() { private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) { 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>() { private class SignedTransactionDeserializer : JsonDeserializer<SignedTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction { override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
val wrapper = parser.readValueAs<SignedTransactionWrapper>() val wrapper = parser.readValueAs<StxJson>()
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures) 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) @JsonSerialize(using = SerializedBytesSerializer::class)
@JsonDeserialize(using = SerializedBytesDeserializer::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.annotation.JacksonAnnotationsInside
import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser 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.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.annotation.JsonSerialize 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.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 @JacksonAnnotationsInside
@JsonSerialize(using = ToStringSerializer::class) @JsonSerialize(using = ToStringSerializer::class)
annotation class ToStringSerialize 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

@ -13,17 +13,20 @@ package net.corda.client.jackson
import com.fasterxml.jackson.core.JsonFactory import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.BinaryNode 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.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.convertValue import com.fasterxml.jackson.module.kotlin.convertValue
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.client.jackson.internal.childrenAs
import net.corda.client.jackson.internal.valueAs 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.cordapp.CordappProvider
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.identity.* import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -31,7 +34,10 @@ import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction 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.core.utilities.*
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.crypto.x509Certificates
@ -49,10 +55,11 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters import org.junit.runners.Parameterized.Parameters
import java.math.BigInteger import java.math.BigInteger
import java.nio.charset.StandardCharsets.* import java.nio.charset.StandardCharsets.UTF_8
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Instant
import java.util.* import java.util.*
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -62,7 +69,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
private companion object { private companion object {
val SEED: BigInteger = BigInteger.valueOf(20170922L) val SEED: BigInteger = BigInteger.valueOf(20170922L)
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey 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 DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
@ -86,6 +93,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
services = rigorousMock() services = rigorousMock()
cordappProvider = rigorousMock() cordappProvider = rigorousMock()
doReturn(cordappProvider).whenever(services).cordappProvider doReturn(cordappProvider).whenever(services).cordappProvider
doReturn(testNetworkParameters()).whenever(services).networkParameters
} }
@Test @Test
@ -192,43 +200,190 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@Test @Test
fun TransactionSignature() { fun TransactionSignature() {
val metadata = SignatureMetadata(1, 1) val signatureMetadata = SignatureMetadata(1, 1)
val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, metadata) 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 json = mapper.valueToTree<ObjectNode>(transactionSignature)
val (bytes, by, signatureMetadata, partialMerkleTree) = json.assertHasOnlyFields( val (bytesJson, byJson, signatureMetadataJson, partialMerkleTreeJson) = json.assertHasOnlyFields(
"bytes", "bytes",
"by", "by",
"signatureMetadata", "signatureMetadata",
"partialMerkleTree" "partialMerkleTree"
) )
assertThat(bytes.binaryValue()).isEqualTo(transactionSignature.bytes) assertThat(bytesJson.binaryValue()).isEqualTo(transactionSignature.bytes)
assertThat(by.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY) assertThat(byJson.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
assertThat(signatureMetadata.valueAs<SignatureMetadata>(mapper)).isEqualTo(metadata) assertThat(signatureMetadataJson.valueAs<SignatureMetadata>(mapper)).isEqualTo(signatureMetadata)
assertThat(partialMerkleTree.isNull).isTrue() assertThat(partialMerkleTreeJson.valueAs<PartialMerkleTree>(mapper).root).isEqualTo(partialMerkleTree.root)
assertThat(mapper.convertValue<TransactionSignature>(json)).isEqualTo(transactionSignature) assertThat(mapper.convertValue<TransactionSignature>(json)).isEqualTo(transactionSignature)
} }
// TODO Add test for PartialMerkleTree
@Test @Test
fun SignedTransaction() { fun `SignedTransaction (WireTransaction)`() {
val attachmentRef = SecureHash.randomSHA256() val attachmentId = SecureHash.randomSHA256()
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) doReturn(attachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
doReturn(testNetworkParameters()).whenever(services).networkParameters val wtx = TransactionBuilder(
notary = DUMMY_NOTARY,
val stx = makeDummyStx() 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) val json = mapper.valueToTree<ObjectNode>(stx)
println(mapper.writeValueAsString(json)) println(mapper.writeValueAsString(json))
val (txBits, signatures) = json.assertHasOnlyFields("txBits", "signatures") val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
assertThat(txBits.binaryValue()).isEqualTo(stx.txBits.bytes) assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs)
val sigs = signatures.elements().asSequence().map { it.valueAs<TransactionSignature>(mapper) }.toList() val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "privacySalt")
assertThat(sigs).isEqualTo(stx.sigs) 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) 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 @Test
fun CordaX500Name() { 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 @Test
@ -472,14 +627,36 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
assertThat(mapper.convertValue<NonCtorPropertiesData>(json)).isEqualTo(data) assertThat(mapper.convertValue<NonCtorPropertiesData>(json)).isEqualTo(data)
} }
private fun makeDummyStx(): SignedTransaction { @Test
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) fun `kotlin object`() {
.toWireTransaction(services) 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( 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)) 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) { private inline fun <reified T : Any> testToStringSerialisation(value: T) {
@ -505,6 +682,11 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
val nonCtor: Int get() = value val nonCtor: Int get() = value
} }
private object KotlinObject
@CordaSerializable
private object CordaSerializableKotlinObject
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper { private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
override var isFullParties: Boolean = false override var isFullParties: Boolean = false
val identities = ArrayList<Party>() val identities = ArrayList<Party>()

View File

@ -230,7 +230,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. * 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. * 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]. * @param schemeCodeName a [String] that should match a supported signature scheme code name (e.g. ECDSA_SECP256K1_SHA256), see [Crypto].

View File

@ -16,8 +16,9 @@ import java.security.spec.AlgorithmParameterSpec
/** /**
* This class is used to define a digital signature scheme. * 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 schemeNumberID unique number ID for better efficiency on-wire serialisation.
* @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 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 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 * @param alternativeOIDs ASN.1 algorithm identifiers for keys of the signature, where we want to map multiple keys to
* the same signature scheme. * the same signature scheme.

View File

@ -17,12 +17,7 @@ import com.google.common.hash.HashingInputStream
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig import net.corda.core.cordapp.CordappConfig
import net.corda.core.cordapp.CordappContext import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.Crypto import net.corda.core.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.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
@ -47,6 +42,7 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.math.BigDecimal import java.math.BigDecimal
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -61,23 +57,11 @@ import java.nio.file.Paths
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.*
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.time.Duration import java.time.Duration
import java.time.temporal.Temporal import java.time.temporal.Temporal
import java.util.* import java.util.*
import java.util.Spliterator.DISTINCT import java.util.Spliterator.*
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.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.stream.IntStream import java.util.stream.IntStream
@ -321,6 +305,23 @@ fun <T : Any> KClass<T>.objectOrNewInstance(): T {
return this.objectInstance ?: this.createInstance() 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 * A simple wrapper around a [Field] object providing type safe read and write access using [value], ignoring the field's
* visibility. * visibility.
@ -395,6 +396,12 @@ inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifie
inline val Class<*>.isConcreteClass: Boolean get() = !isInterface && !isAbstractClass 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 URI.toPath(): Path = Paths.get(this)
fun URL.toPath(): Path = toURI().toPath() fun URL.toPath(): Path = toURI().toPath()

View File

@ -10,7 +10,8 @@
package net.corda.core.internal 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.Assert.assertArrayEquals
import org.junit.Test import org.junit.Test
import java.util.stream.IntStream import java.util.stream.IntStream
@ -18,29 +19,29 @@ import java.util.stream.Stream
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class InternalUtilsTest { open class InternalUtilsTest {
@Test @Test
fun `noneOrSingle on an empty collection`() { fun `noneOrSingle on an empty collection`() {
val collection = emptyList<Int>() val collection = emptyList<Int>()
Assertions.assertThat(collection.noneOrSingle()).isNull() assertThat(collection.noneOrSingle()).isNull()
Assertions.assertThat(collection.noneOrSingle { it == 1 }).isNull() assertThat(collection.noneOrSingle { it == 1 }).isNull()
} }
@Test @Test
fun `noneOrSingle on a singleton collection`() { fun `noneOrSingle on a singleton collection`() {
val collection = listOf(1) val collection = listOf(1)
Assertions.assertThat(collection.noneOrSingle()).isEqualTo(1) assertThat(collection.noneOrSingle()).isEqualTo(1)
Assertions.assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1) assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1)
Assertions.assertThat(collection.noneOrSingle { it == 2 }).isNull() assertThat(collection.noneOrSingle { it == 2 }).isNull()
} }
@Test @Test
fun `noneOrSingle on a collection with two items`() { fun `noneOrSingle on a collection with two items`() {
val collection = listOf(1, 2) val collection = listOf(1, 2)
assertFailsWith<IllegalArgumentException> { collection.noneOrSingle() } assertFailsWith<IllegalArgumentException> { collection.noneOrSingle() }
Assertions.assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1) assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1)
Assertions.assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2) assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2)
Assertions.assertThat(collection.noneOrSingle { it == 3 }).isNull() assertThat(collection.noneOrSingle { it == 3 }).isNull()
assertFailsWith<IllegalArgumentException> { collection.noneOrSingle { it > 0 } } assertFailsWith<IllegalArgumentException> { collection.noneOrSingle { it > 0 } }
} }
@ -49,7 +50,7 @@ class InternalUtilsTest {
val collection = listOf(1, 2, 1) val collection = listOf(1, 2, 1)
assertFailsWith<IllegalArgumentException> { collection.noneOrSingle() } assertFailsWith<IllegalArgumentException> { collection.noneOrSingle() }
assertFailsWith<IllegalArgumentException> { collection.noneOrSingle { it == 1 } } assertFailsWith<IllegalArgumentException> { collection.noneOrSingle { it == 1 } }
Assertions.assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2) assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2)
} }
@Test @Test
@ -98,4 +99,19 @@ class InternalUtilsTest {
assertEquals(Array<String?>::class.java, b.javaClass) assertEquals(Array<String?>::class.java, b.javaClass)
assertArrayEquals(arrayOf("one", "two", null), b) 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

@ -8,6 +8,8 @@ Unreleased
========== ==========
* Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code. * Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code.
* Fixed an issue with ``CashException`` not being able to deserialise after the introduction of AMQP for RPC.
* Removed -xmx VM argument from Explorer's Capsule setup. This helps avoiding out of memory errors. * Removed -xmx VM argument from Explorer's Capsule setup. This helps avoiding out of memory errors.
* Shell now kills an ongoing flow when CTRL+C is pressed in the terminal. * Shell now kills an ongoing flow when CTRL+C is pressed in the terminal.
@ -54,7 +56,10 @@ Unreleased
The encoded bytes are also serialised into the ``encoded`` field. This can be used to deserialise an ``X509Certificate`` The encoded bytes are also serialised into the ``encoded`` field. This can be used to deserialise an ``X509Certificate``
back. back.
* ``CertPath`` objects are serialised as a list of ``X509Certificate`` objects. * ``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`` * ``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`` then ``Party`` objects are serialised as JSON objects with the ``name`` and ``owningKey`` fields. For ``PartyAndCertificate``

View File

@ -54,7 +54,7 @@ For instance, **network map** is ECDSA NIST P-256 (secp256r1) in the Corda Netwo
underlying HSM device, but the default for dev-mode is Pure EdDSA (ed25519). underlying HSM device, but the default for dev-mode is Pure EdDSA (ed25519).
The following table presents the 5 signature schemes currently supported by Corda. The TLS column shows which of them The following table presents the 5 signature schemes currently supported by Corda. The TLS column shows which of them
are compatible with TLS 1.2, while the default scheme per key type is also shown. are compatible with TLS 1.2, while the default scheme per key type is also shown in the last column.
+-------------------------+---------------------------------------------------------------+-----+-------------------------+ +-------------------------+---------------------------------------------------------------+-----+-------------------------+
| Cipher suite | Description | TLS | Default for | | Cipher suite | Description | TLS | Default for |
@ -93,7 +93,7 @@ are compatible with TLS 1.2, while the default scheme per key type is also shown
+-------------------------+---------------------------------------------------------------+-----+-------------------------+ +-------------------------+---------------------------------------------------------------+-----+-------------------------+
| | SPHINCS-256 | | SPHINCS-256 is a post-quantum secure algorithm that relies | NO | | | | SPHINCS-256 | | SPHINCS-256 is a post-quantum secure algorithm that relies | NO | |
| | and SHA-512 | | only on hash functions. It is included as a hedge against | | | | | and SHA-512 | | only on hash functions. It is included as a hedge against | | |
| | | the possibility of a malicious adversary obtaining a | | | | | (experimental) | | the possibility of a malicious adversary obtaining a | | |
| | | quantum computer capable of running Shor's algorithm in | | | | | | quantum computer capable of running Shor's algorithm in | | |
| | | future. SPHINCS is based ultimately on a clever usage of | | | | | | future. SPHINCS is based ultimately on a clever usage of | | |
| | | Merkle hash trees. Hash functions are a very heavily | | | | | | Merkle hash trees. Hash functions are a very heavily | | |

View File

@ -75,6 +75,8 @@ certificates must obey the following restrictions:
* ECDSA using the NIST P-256 curve (secp256r1) * ECDSA using the NIST P-256 curve (secp256r1)
* ECDSA using the Koblitz k1 curve (secp256k1)
* RSA with 3072-bit key size * RSA with 3072-bit key size
.. note:: Corda's ``X509Utilities`` show how to generate the required public/private keypairs and certificates using .. note:: Corda's ``X509Utilities`` show how to generate the required public/private keypairs and certificates using

View File

@ -0,0 +1,35 @@
package net.corda.finance.compat
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.finance.flows.CashException
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
class CashExceptionSerialisationTest {
@Test
fun `cash exception with a cause can be serialised with AMQP`() {
driver(DriverParameters(startNodesInProcess = true)) {
val node = startNode(rpcUsers = listOf(User("mark", "dadada", setOf(all())))).getOrThrow()
val action = { node.rpc.startFlow(::CashExceptionThrowingFlow).returnValue.getOrThrow() }
assertThatThrownBy(action).isInstanceOfSatisfying(CashException::class.java) { thrown ->
assertThat(thrown).hasNoCause()
assertThat(thrown.stackTrace).isEmpty()
}
}
}
}
@StartableByRPC
class CashExceptionThrowingFlow : FlowLogic<Unit>() {
override fun call() {
throw CashException("BOOM!", IllegalStateException("Nope dude!"))
}
}

View File

@ -60,4 +60,7 @@ abstract class AbstractCashFlow<out T>(override val progressTracker: ProgressTra
abstract class AbstractRequest(val amount: Amount<Currency>) abstract class AbstractRequest(val amount: Amount<Currency>)
} }
class CashException(message: String, cause: Throwable) : FlowException(message, cause) // The internal constructor here allows the ThrowableSerializer to find a suitable constructor even if the `cause` field is removed using reflection by the RPC proxies.
class CashException internal constructor(cause: Throwable?, message: String) : FlowException(message, cause) {
constructor(message: String, cause: Throwable) : this(cause, message)
}

View File

@ -12,13 +12,9 @@
package net.corda.nodeapi.internal.config package net.corda.nodeapi.internal.config
import com.typesafe.config.Config import com.typesafe.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 net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.isStatic
import net.corda.core.internal.noneOrSingle import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
@ -26,7 +22,6 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier.isStatic
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.net.Proxy import java.net.Proxy
import java.net.URL import java.net.URL
@ -219,7 +214,7 @@ fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig(
private fun Any.toConfigMap(): Map<String, Any> { private fun Any.toConfigMap(): Map<String, Any> {
val values = HashMap<String, Any>() val values = HashMap<String, Any>()
for (field in javaClass.declaredFields) { for (field in javaClass.declaredFields) {
if (isStatic(field.modifiers) || field.isSynthetic) continue if (field.isStatic || field.isSynthetic) continue
field.isAccessible = true field.isAccessible = true
val value = field.get(this) ?: continue val value = field.get(this) ?: continue
val configValue = if (value is String || value is Boolean || value is Number) { val configValue = if (value is String || value is Boolean || value is Number) {

View File

@ -16,6 +16,7 @@ import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.Util import com.esotericsoftware.kryo.util.Util
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.writer import net.corda.core.internal.writer
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
@ -25,7 +26,6 @@ import net.corda.serialization.internal.MutableClassWhitelist
import net.corda.serialization.internal.TransientClassWhiteList import net.corda.serialization.internal.TransientClassWhiteList
import net.corda.serialization.internal.amqp.hasCordaSerializable import net.corda.serialization.internal.amqp.hasCordaSerializable
import java.io.PrintWriter import java.io.PrintWriter
import java.lang.reflect.Modifier
import java.lang.reflect.Modifier.isAbstract import java.lang.reflect.Modifier.isAbstract
import java.nio.charset.StandardCharsets.UTF_8 import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.Paths import java.nio.file.Paths
@ -85,22 +85,7 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl
override fun registerImplicit(type: Class<*>): Registration { override fun registerImplicit(type: Class<*>): Registration {
val targetType = typeForSerializationOf(type) val targetType = typeForSerializationOf(type)
// Is this a Kotlin object? We use our own reflection here rather than .kotlin.objectInstance because Kotlin val objectInstance = targetType.kotlinObjectInstance
// 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
}
// We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent. // 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 val references = kryo.references

View File

@ -31,7 +31,11 @@ import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.serialization.* import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
@ -42,9 +46,12 @@ import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.config.shouldCheckCheckpoints import net.corda.node.services.config.shouldCheckCheckpoints
import net.corda.node.services.messaging.DeduplicationHandler import net.corda.node.services.messaging.DeduplicationHandler
import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.ReceivedMessage
import net.corda.node.services.statemachine.interceptors.* import net.corda.node.services.statemachine.interceptors.DumpHistoryOnErrorInterceptor
import net.corda.node.services.statemachine.interceptors.FiberDeserializationChecker
import net.corda.node.services.statemachine.interceptors.FiberDeserializationCheckingInterceptor
import net.corda.node.services.statemachine.interceptors.HospitalisingInterceptor
import net.corda.node.services.statemachine.interceptors.PrintingInterceptor
import net.corda.node.services.statemachine.transitions.StateMachine import net.corda.node.services.statemachine.transitions.StateMachine
import net.corda.node.services.statemachine.transitions.StateMachineConfiguration
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
@ -287,8 +294,6 @@ class SingleThreadedStateMachineManager(
} }
} }
private val stateMachineConfiguration = serviceHub.configuration.enterpriseConfiguration.tuning.stateMachine
private fun checkQuasarJavaAgentPresence() { private fun checkQuasarJavaAgentPresence() {
check(SuspendableHelper.isJavaAgentActive(), { check(SuspendableHelper.isJavaAgentActive(), {
"""Missing the '-javaagent' JVM argument. Make sure you run the tests with the Quasar java agent attached to your JVM. """Missing the '-javaagent' JVM argument. Make sure you run the tests with the Quasar java agent attached to your JVM.
@ -581,7 +586,7 @@ class SingleThreadedStateMachineManager(
database = database, database = database,
transitionExecutor = transitionExecutor, transitionExecutor = transitionExecutor,
actionExecutor = actionExecutor!!, actionExecutor = actionExecutor!!,
stateMachine = StateMachine(id, stateMachineConfiguration, secureRandom), stateMachine = StateMachine(id, secureRandom),
serviceHub = serviceHub, serviceHub = serviceHub,
checkpointSerializationContext = checkpointSerializationContext!! checkpointSerializationContext = checkpointSerializationContext!!
) )

View File

@ -10,8 +10,9 @@
package net.corda.node.services.statemachine.transitions package net.corda.node.services.statemachine.transitions
import net.corda.core.flows.* import net.corda.core.flows.StateMachineRunId
import net.corda.node.services.statemachine.* import net.corda.node.services.statemachine.Event
import net.corda.node.services.statemachine.StateMachineState
import java.security.SecureRandom import java.security.SecureRandom
/** /**
@ -37,30 +38,11 @@ enum class SessionDeliverPersistenceStrategy {
OnNextCommit OnNextCommit
} }
/**
* @property sessionDeliverPersistenceStrategy see [SessionDeliverPersistenceStrategy]
* @property eventQueueSize the size of a flow's event queue. If the queue gets full the thread scheduling the event
* will block. An example scenario would be if the flow is waiting for a lot of messages at once, but is slow at
* processing each.
*/
data class StateMachineConfiguration(
val sessionDeliverPersistenceStrategy: SessionDeliverPersistenceStrategy,
val eventQueueSize: Int
) {
companion object {
val default = StateMachineConfiguration(
sessionDeliverPersistenceStrategy = SessionDeliverPersistenceStrategy.OnDeliver,
eventQueueSize = 16
)
}
}
class StateMachine( class StateMachine(
val id: StateMachineRunId, val id: StateMachineRunId,
val configuration: StateMachineConfiguration,
val secureRandom: SecureRandom val secureRandom: SecureRandom
) { ) {
fun transition(event: Event, state: StateMachineState): TransitionResult { fun transition(event: Event, state: StateMachineState): TransitionResult {
return TopLevelTransition(TransitionContext(id, configuration, secureRandom), state, event).transition() return TopLevelTransition(TransitionContext(id, secureRandom), state, event).transition()
} }
} }

View File

@ -38,6 +38,5 @@ interface Transition {
class TransitionContext( class TransitionContext(
val id: StateMachineRunId, val id: StateMachineRunId,
val configuration: StateMachineConfiguration,
val secureRandom: SecureRandom val secureRandom: SecureRandom
) )

View File

@ -10,8 +10,8 @@
package net.corda.node.utilities package net.corda.node.utilities
import net.corda.core.internal.isStatic
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.Type import java.lang.reflect.Type
import java.time.Instant import java.time.Instant
@ -141,7 +141,7 @@ object ObjectDiffer {
private fun getFieldFoci(obj: Any) : List<FieldFocus> { private fun getFieldFoci(obj: Any) : List<FieldFocus> {
val foci = ArrayList<FieldFocus>() val foci = ArrayList<FieldFocus>()
for (method in obj.javaClass.declaredMethods) { for (method in obj.javaClass.declaredMethods) {
if (Modifier.isStatic(method.modifiers)) { if (method.isStatic) {
continue continue
} }
if (method.name.startsWith("get") && method.name.length > 3 && method.parameterCount == 0) { if (method.name.startsWith("get") && method.name.length > 3 && method.parameterCount == 0) {

View File

@ -12,6 +12,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.hash.Hasher import com.google.common.hash.Hasher
import com.google.common.hash.Hashing import com.google.common.hash.Hashing
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toBase64 import net.corda.core.utilities.toBase64
import java.io.NotSerializableException import java.io.NotSerializableException
@ -163,7 +164,7 @@ class SerializerFingerPrinter : FingerPrinter {
}.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH) }.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH)
} else { } else {
hasher.fingerprintWithCustomSerializerOrElse(factory!!, type, type) { 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 // 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. // to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name) hasher.putUnencodedChars(type.name)

View File

@ -13,6 +13,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeToken import com.google.common.reflect.TypeToken
import net.corda.core.internal.isConcreteClass import net.corda.core.internal.isConcreteClass
import net.corda.core.internal.isPublic
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -165,7 +166,7 @@ fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
// In addition, only getters that take zero parameters and setters that take a single // In addition, only getters that take zero parameters and setters that take a single
// parameter will be considered // parameter will be considered
clazz!!.declaredMethods?.map { func -> clazz!!.declaredMethods?.map { func ->
if (!Modifier.isPublic(func.modifiers)) return@map if (!func.isPublic) return@map
if (func.name == "getClass") return@map if (func.name == "getClass") return@map
PropertyDescriptorsRegex.re.find(func.name)?.apply { PropertyDescriptorsRegex.re.find(func.name)?.apply {
@ -547,49 +548,3 @@ fun hasCordaSerializable(type: Class<*>): Boolean {
|| type.interfaces.any(::hasCordaSerializable) || type.interfaces.any(::hasCordaSerializable)
|| (type.superclass != null && hasCordaSerializable(type.superclass)) || (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

@ -12,6 +12,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeResolver import com.google.common.reflect.TypeResolver
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
@ -339,7 +340,7 @@ open class SerializerFactory(
if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this) if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this)
else ArraySerializer.make(type, this) else ArraySerializer.make(type, this)
} else { } else {
val singleton = clazz.objectInstance() val singleton = clazz.kotlinObjectInstance
if (singleton != null) { if (singleton != null) {
whitelist.requireWhitelisted(clazz) whitelist.requireWhitelisted(clazz)
SingletonSerializer(clazz, singleton, this) SingletonSerializer(clazz, singleton, this)