mirror of
https://github.com/corda/corda.git
synced 2025-06-01 15:10:54 +00:00
Merge pull request #895 from corda/os-merge-5d1cc0b
O/S merge from 5d1cc0b
This commit is contained in:
commit
27e688e2c8
@ -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'
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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>()
|
||||||
|
@ -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].
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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``
|
||||||
|
@ -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 | | |
|
||||||
|
@ -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
|
||||||
|
@ -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!"))
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -52,10 +52,6 @@ enterpriseConfiguration = {
|
|||||||
maximumMessagingBatchSize = 256
|
maximumMessagingBatchSize = 256
|
||||||
p2pConfirmationWindowSize = 1048576
|
p2pConfirmationWindowSize = 1048576
|
||||||
brokerConnectionTtlCheckIntervalMs = 20
|
brokerConnectionTtlCheckIntervalMs = 20
|
||||||
stateMachine = {
|
|
||||||
eventQueueSize = 16
|
|
||||||
sessionDeliverPersistenceStrategy = "OnNextCommit"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
useMultiThreadedSMM = true
|
useMultiThreadedSMM = true
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
package net.corda.node.services.config
|
package net.corda.node.services.config
|
||||||
|
|
||||||
import net.corda.node.services.statemachine.transitions.StateMachineConfiguration
|
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
data class EnterpriseConfiguration(
|
data class EnterpriseConfiguration(
|
||||||
@ -46,8 +45,7 @@ data class PerformanceTuning(
|
|||||||
val maximumMessagingBatchSize: Int,
|
val maximumMessagingBatchSize: Int,
|
||||||
val rpcThreadPoolSize: Int,
|
val rpcThreadPoolSize: Int,
|
||||||
val p2pConfirmationWindowSize: Int,
|
val p2pConfirmationWindowSize: Int,
|
||||||
val brokerConnectionTtlCheckIntervalMs: Long,
|
val brokerConnectionTtlCheckIntervalMs: Long
|
||||||
val stateMachine: StateMachineConfiguration
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val default = PerformanceTuning(
|
val default = PerformanceTuning(
|
||||||
@ -55,8 +53,7 @@ data class PerformanceTuning(
|
|||||||
maximumMessagingBatchSize = 256,
|
maximumMessagingBatchSize = 256,
|
||||||
rpcThreadPoolSize = 4,
|
rpcThreadPoolSize = 4,
|
||||||
p2pConfirmationWindowSize = 1048576,
|
p2pConfirmationWindowSize = 1048576,
|
||||||
brokerConnectionTtlCheckIntervalMs = 20,
|
brokerConnectionTtlCheckIntervalMs = 20
|
||||||
stateMachine = StateMachineConfiguration.default
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,20 +23,12 @@ import net.corda.core.flows.FlowInfo
|
|||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.ConcurrentBox
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.internal.FlowStateMachine
|
|
||||||
import net.corda.core.internal.LifeCycle
|
|
||||||
import net.corda.core.internal.bufferUntilSubscribed
|
|
||||||
import net.corda.core.internal.castIfPossible
|
|
||||||
import net.corda.core.internal.concurrent.OpenFuture
|
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.SerializationContext
|
import net.corda.core.serialization.*
|
||||||
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
|
||||||
@ -47,12 +39,7 @@ 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.DumpHistoryOnErrorInterceptor
|
import net.corda.node.services.statemachine.interceptors.*
|
||||||
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.MetricInterceptor
|
|
||||||
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.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
@ -299,8 +286,6 @@ class MultiThreadedStateMachineManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
@ -593,7 +578,7 @@ class MultiThreadedStateMachineManager(
|
|||||||
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!!
|
||||||
)
|
)
|
||||||
|
@ -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!!
|
||||||
)
|
)
|
||||||
|
@ -10,57 +10,16 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies what strategy to use to persist received messages.
|
|
||||||
*
|
|
||||||
* - [OnDeliver] means the received message should be persisted in a checkpoint as soon as possible. This means that the
|
|
||||||
* next time the flow enters the state machine a checkpoint will be created with the current state and the received
|
|
||||||
* message. Note that the deduplication ID of the received message will be committed together with the checkpoint.
|
|
||||||
* This means that for each [FlowSession.receive] *two* checkpoints will be created, one when receive() is called,
|
|
||||||
* and one when the message is received. It also means that internal session messages not exposed to the flow also
|
|
||||||
* create checkpoints.
|
|
||||||
* - [OnNextCommit] means that instead of creating an explicit checkpoint we wait for the next one that would happen
|
|
||||||
* anyway. During this time the message will not be acknowledged.
|
|
||||||
* Note that this also means that if the flow is completely idempotent then the message will never be persisted as
|
|
||||||
* no checkpoints are ever committed (unless the flow errors). In this case the message will be acknowledged at the
|
|
||||||
* very end of the flow.
|
|
||||||
* In general turning this on is safe and much more efficient than [OnDeliver]. However if the flow is hogging the
|
|
||||||
* fiber (for example doing some IO) then the acknowledgement window of the received message will be extended to
|
|
||||||
* an arbitrary length.
|
|
||||||
*/
|
|
||||||
enum class SessionDeliverPersistenceStrategy {
|
|
||||||
OnDeliver,
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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) {
|
||||||
|
@ -37,10 +37,6 @@ enterpriseConfiguration = {
|
|||||||
maximumMessagingBatchSize = 256
|
maximumMessagingBatchSize = 256
|
||||||
p2pConfirmationWindowSize = 1048576
|
p2pConfirmationWindowSize = 1048576
|
||||||
brokerConnectionTtlCheckIntervalMs = 20
|
brokerConnectionTtlCheckIntervalMs = 20
|
||||||
stateMachine = {
|
|
||||||
eventQueueSize = 16
|
|
||||||
sessionDeliverPersistenceStrategy = "OnNextCommit"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
useMultiThreadedSMM = true
|
useMultiThreadedSMM = true
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,6 @@ enterpriseConfiguration = {
|
|||||||
maximumMessagingBatchSize = 256
|
maximumMessagingBatchSize = 256
|
||||||
p2pConfirmationWindowSize = 1048576
|
p2pConfirmationWindowSize = 1048576
|
||||||
brokerConnectionTtlCheckIntervalMs = 20
|
brokerConnectionTtlCheckIntervalMs = 20
|
||||||
stateMachine = {
|
|
||||||
eventQueueSize = 16
|
|
||||||
sessionDeliverPersistenceStrategy = "OnNextCommit"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
useMultiThreadedSMM = true
|
useMultiThreadedSMM = true
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user