Merge pull request #895 from corda/os-merge-5d1cc0b

O/S merge from 5d1cc0b
This commit is contained in:
Shams Asari 2018-05-31 11:14:15 +01:00 committed by GitHub
commit 27e688e2c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 633 additions and 265 deletions

View File

@ -49,7 +49,7 @@ buildscript {
* https://issues.apache.org/jira/browse/ARTEMIS-1559
*/
ext.artemis_version = '2.5.0'
ext.jackson_version = '2.9.3'
ext.jackson_version = '2.9.5'
ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
ext.assertj_version = '3.8.0'

View File

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

View File

@ -2,30 +2,39 @@
package net.corda.client.jackson.internal
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.IntNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import com.google.common.primitives.Booleans
import net.corda.client.jackson.JacksonSupport
import net.corda.core.contracts.Amount
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.*
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.parseAsHex
import net.corda.core.utilities.toHexString
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.constructorForDeserialization
@ -33,6 +42,7 @@ import net.corda.serialization.internal.amqp.hasCordaSerializable
import net.corda.serialization.internal.amqp.propertiesForSerialization
import java.security.PublicKey
import java.security.cert.CertPath
import java.time.Instant
class CordaModule : SimpleModule("corda-core") {
override fun setupModule(context: SetupContext) {
@ -50,12 +60,20 @@ class CordaModule : SimpleModule("corda-core") {
context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java)
context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java)
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
context.setMixInAnnotations(SecureHash::class.java, SecureHashSHA256Mixin::class.java)
context.setMixInAnnotations(SerializedBytes::class.java, SerializedBytesMixin::class.java)
context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(SignedTransaction::class.java, SignedTransactionMixin::class.java)
context.setMixInAnnotations(WireTransaction::class.java, JacksonSupport.WireTransactionMixin::class.java)
context.setMixInAnnotations(WireTransaction::class.java, WireTransactionMixin::class.java)
context.setMixInAnnotations(TransactionState::class.java, TransactionStateMixin::class.java)
context.setMixInAnnotations(Command::class.java, CommandMixin::class.java)
context.setMixInAnnotations(TimeWindow::class.java, TimeWindowMixin::class.java)
context.setMixInAnnotations(PrivacySalt::class.java, PrivacySaltMixin::class.java)
context.setMixInAnnotations(SignatureScheme::class.java, SignatureSchemeMixin::class.java)
context.setMixInAnnotations(SignatureMetadata::class.java, SignatureMetadataMixin::class.java)
context.setMixInAnnotations(PartialTree::class.java, PartialTreeMixin::class.java)
context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java)
}
}
@ -64,21 +82,23 @@ class CordaModule : SimpleModule("corda-core") {
* Use the same properties that AMQP serialization uses if the POJO is @CordaSerializable
*/
private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() {
// We need a SerializerFactory when scanning for properties but don't actually use it so any will do
private val serializerFactory = SerializerFactory(AllWhitelist, Thread.currentThread().contextClassLoader)
// We need to pass in a SerializerFactory when scanning for properties, but don't actually do any serialisation so any will do.
private val serializerFactory = SerializerFactory(AllWhitelist, javaClass.classLoader)
override fun changeProperties(config: SerializationConfig,
beanDesc: BeanDescription,
beanProperties: MutableList<BeanPropertyWriter>): MutableList<BeanPropertyWriter> {
if (hasCordaSerializable(beanDesc.beanClass)) {
val ctor = constructorForDeserialization(beanDesc.beanClass)
val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory)
val beanClass = beanDesc.beanClass
if (hasCordaSerializable(beanClass) && beanClass.kotlinObjectInstance == null) {
val ctor = constructorForDeserialization(beanClass)
val amqpProperties = propertiesForSerialization(ctor, beanClass, serializerFactory)
.serializationOrder
.map { it.serializer.name }
beanProperties.removeIf { it.name !in amqpProperties }
(amqpProperties - beanProperties.map { it.name }).let {
val propertyRenames = beanDesc.findProperties().associateBy({ it.name }, { it.internalName })
(amqpProperties - propertyRenames.values).let {
check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" }
}
beanProperties.removeIf { propertyRenames[it.name] !in amqpProperties }
}
return beanProperties
}
@ -88,11 +108,7 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier()
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
private interface NetworkHostAndPortMixin
private class NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
return NetworkHostAndPort.parse(parser.text)
}
}
private class NetworkHostAndPortDeserializer : SimpleDeserializer<NetworkHostAndPort>({ NetworkHostAndPort.parse(text) })
@JsonSerialize(using = PartyAndCertificateSerializer::class)
// TODO Add deserialization which follows the same lookup logic as Party
@ -102,14 +118,14 @@ private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
val mapper = gen.codec as JacksonSupport.PartyObjectMapper
if (mapper.isFullParties) {
gen.writeObject(PartyAndCertificateWrapper(value.name, value.certPath))
gen.writeObject(PartyAndCertificateJson(value.name, value.certPath))
} else {
gen.writeObject(value.party)
}
}
}
private class PartyAndCertificateWrapper(val name: CordaX500Name, val certPath: CertPath)
private class PartyAndCertificateJson(val name: CordaX500Name, val certPath: CertPath)
@JsonSerialize(using = SignedTransactionSerializer::class)
@JsonDeserialize(using = SignedTransactionDeserializer::class)
@ -117,18 +133,226 @@ private interface SignedTransactionMixin
private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
val core = value.coreTransaction
val stxJson = when (core) {
is WireTransaction -> StxJson(wire = core, signatures = value.sigs)
is FilteredTransaction -> StxJson(filtered = core, signatures = value.sigs)
is NotaryChangeWireTransaction -> StxJson(notaryChangeWire = core, signatures = value.sigs)
is ContractUpgradeWireTransaction -> StxJson(contractUpgradeWire = core, signatures = value.sigs)
is ContractUpgradeFilteredTransaction -> StxJson(contractUpgradeFiltered = core, signatures = value.sigs)
else -> throw IllegalArgumentException("Don't know about ${core.javaClass}")
}
gen.writeObject(stxJson)
}
}
private class SignedTransactionDeserializer : JsonDeserializer<SignedTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
val wrapper = parser.readValueAs<SignedTransactionWrapper>()
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
val wrapper = parser.readValueAs<StxJson>()
val core = wrapper.run { wire ?: filtered ?: notaryChangeWire ?: contractUpgradeWire ?: contractUpgradeFiltered!! }
return SignedTransaction(core, wrapper.signatures)
}
}
private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List<TransactionSignature>)
@JsonInclude(Include.NON_NULL)
private data class StxJson(
val wire: WireTransaction? = null,
val filtered: FilteredTransaction? = null,
val notaryChangeWire: NotaryChangeWireTransaction? = null,
val contractUpgradeWire: ContractUpgradeWireTransaction? = null,
val contractUpgradeFiltered: ContractUpgradeFilteredTransaction? = null,
val signatures: List<TransactionSignature>
) {
init {
val count = Booleans.countTrue(wire != null, filtered != null, notaryChangeWire != null, contractUpgradeWire != null, contractUpgradeFiltered != null)
require(count == 1) { this }
}
}
@JsonSerialize(using = WireTransactionSerializer::class)
@JsonDeserialize(using = WireTransactionDeserializer::class)
private interface WireTransactionMixin
private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
override fun serialize(value: WireTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(WireTransactionJson(
value.id,
value.notary,
value.inputs,
value.outputs,
value.commands,
value.timeWindow,
value.attachments,
value.privacySalt
))
}
}
private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): WireTransaction {
val wrapper = parser.readValueAs<WireTransactionJson>()
val componentGroups = WireTransaction.createComponentGroups(
wrapper.inputs,
wrapper.outputs,
wrapper.commands,
wrapper.attachments,
wrapper.notary,
wrapper.timeWindow
)
return WireTransaction(componentGroups, wrapper.privacySalt)
}
}
private class WireTransactionJson(val id: SecureHash,
val notary: Party?,
val inputs: List<StateRef>,
val outputs: List<TransactionState<*>>,
val commands: List<Command<*>>,
val timeWindow: TimeWindow?,
val attachments: List<SecureHash>,
val privacySalt: PrivacySalt)
private interface TransactionStateMixin {
@get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
val data: ContractState
@get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
val constraint: AttachmentConstraint
}
private interface CommandMixin {
@get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
val value: CommandData
}
@JsonDeserialize(using = TimeWindowDeserializer::class)
private interface TimeWindowMixin
private class TimeWindowDeserializer : JsonDeserializer<TimeWindow>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): TimeWindow {
return parser.readValueAs<TimeWindowJson>().run {
when {
fromTime != null && untilTime != null -> TimeWindow.between(fromTime, untilTime)
fromTime != null -> TimeWindow.fromOnly(fromTime)
untilTime != null -> TimeWindow.untilOnly(untilTime)
else -> throw JsonParseException(parser, "Neither fromTime nor untilTime exists for TimeWindow")
}
}
}
}
private data class TimeWindowJson(val fromTime: Instant?, val untilTime: Instant?)
@JsonSerialize(using = PrivacySaltSerializer::class)
@JsonDeserialize(using = PrivacySaltDeserializer::class)
private interface PrivacySaltMixin
private class PrivacySaltSerializer : JsonSerializer<PrivacySalt>() {
override fun serialize(value: PrivacySalt, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.bytes.toHexString())
}
}
private class PrivacySaltDeserializer : SimpleDeserializer<PrivacySalt>({ PrivacySalt(text.parseAsHex()) })
// TODO Add a lookup function by number ID in Crypto
private val signatureSchemesByNumberID = Crypto.supportedSignatureSchemes().associateBy { it.schemeNumberID }
@JsonSerialize(using = SignatureMetadataSerializer::class)
@JsonDeserialize(using = SignatureMetadataDeserializer::class)
private interface SignatureMetadataMixin
private class SignatureMetadataSerializer : JsonSerializer<SignatureMetadata>() {
override fun serialize(value: SignatureMetadata, gen: JsonGenerator, serializers: SerializerProvider) {
gen.jsonObject {
writeNumberField("platformVersion", value.platformVersion)
writeObjectField("scheme", value.schemeNumberID.let { signatureSchemesByNumberID[it] ?: it })
}
}
}
private class SignatureMetadataDeserializer : JsonDeserializer<SignatureMetadata>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignatureMetadata {
val json = parser.readValueAsTree<ObjectNode>()
val scheme = json["scheme"]
val schemeNumberID = if (scheme is IntNode) {
scheme.intValue()
} else {
Crypto.findSignatureScheme(scheme.textValue()).schemeNumberID
}
return SignatureMetadata(json["platformVersion"].intValue(), schemeNumberID)
}
}
@JsonSerialize(using = PartialTreeSerializer::class)
@JsonDeserialize(using = PartialTreeDeserializer::class)
private interface PartialTreeMixin
private class PartialTreeSerializer : JsonSerializer<PartialTree>() {
override fun serialize(value: PartialTree, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(convert(value))
}
private fun convert(tree: PartialTree): PartialTreeJson {
return when (tree) {
is PartialTree.IncludedLeaf -> PartialTreeJson(includedLeaf = tree.hash)
is PartialTree.Leaf -> PartialTreeJson(leaf = tree.hash)
is PartialTree.Node -> PartialTreeJson(left = convert(tree.left), right = convert(tree.right))
else -> throw IllegalArgumentException("Don't know how to serialize $tree")
}
}
}
private class PartialTreeDeserializer : JsonDeserializer<PartialTree>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): PartialTree {
return convert(parser.readValueAs(PartialTreeJson::class.java))
}
private fun convert(wrapper: PartialTreeJson): PartialTree {
return wrapper.run {
when {
includedLeaf != null -> PartialTree.IncludedLeaf(includedLeaf)
leaf != null -> PartialTree.Leaf(leaf)
else -> PartialTree.Node(convert(left!!), convert(right!!))
}
}
}
}
@JsonInclude(Include.NON_NULL)
private class PartialTreeJson(val includedLeaf: SecureHash? = null,
val leaf: SecureHash? = null,
val left: PartialTreeJson? = null,
val right: PartialTreeJson? = null) {
init {
if (includedLeaf != null) {
require(leaf == null && left == null && right == null)
} else if (leaf != null) {
require(left == null && right == null)
} else {
require(left != null && right != null)
}
}
}
@JsonSerialize(using = SignatureSchemeSerializer::class)
@JsonDeserialize(using = SignatureSchemeDeserializer::class)
private interface SignatureSchemeMixin
private class SignatureSchemeSerializer : JsonSerializer<SignatureScheme>() {
override fun serialize(value: SignatureScheme, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.schemeCodeName)
}
}
private class SignatureSchemeDeserializer : JsonDeserializer<SignatureScheme>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignatureScheme {
return if (parser.currentToken == JsonToken.VALUE_NUMBER_INT) {
signatureSchemesByNumberID[parser.intValue] ?: throw JsonParseException(parser, "Unable to find SignatureScheme ${parser.text}")
} else {
Crypto.findSignatureScheme(parser.text)
}
}
}
@JsonSerialize(using = SerializedBytesSerializer::class)
@JsonDeserialize(using = SerializedBytesDeserializer::class)

View File

@ -3,6 +3,8 @@ package net.corda.client.jackson.internal
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.annotation.JsonSerialize
@ -20,6 +22,14 @@ inline fun <reified T> JsonParser.readValueAs(): T = readValueAs(T::class.java)
inline fun <reified T : Any> JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this)
inline fun <reified T : Any> JsonNode.childrenAs(mapper: ObjectMapper): List<T> {
return elements().asSequence().map { it.valueAs<T>(mapper) }.toList()
}
@JacksonAnnotationsInside
@JsonSerialize(using = ToStringSerializer::class)
annotation class ToStringSerialize
annotation class ToStringSerialize
abstract class SimpleDeserializer<T>(private val func: JsonParser.() -> T) : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): T = func(parser)
}

View File

@ -13,17 +13,20 @@ package net.corda.client.jackson
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.BinaryNode
import com.fasterxml.jackson.databind.node.IntNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.convertValue
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.client.jackson.internal.childrenAs
import net.corda.client.jackson.internal.valueAs
import net.corda.core.contracts.Amount
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo
@ -31,7 +34,10 @@ import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.*
import net.corda.finance.USD
import net.corda.nodeapi.internal.crypto.x509Certificates
@ -49,10 +55,11 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import java.math.BigInteger
import java.nio.charset.StandardCharsets.*
import java.nio.charset.StandardCharsets.UTF_8
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Instant
import java.util.*
import javax.security.auth.x500.X500Principal
import kotlin.collections.ArrayList
@ -62,7 +69,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
private companion object {
val SEED: BigInteger = BigInteger.valueOf(20170922L)
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
val BOB_PUBKEY = TestIdentity(BOB_NAME, 70).publicKey
val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).publicKey
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
@ -86,6 +93,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
services = rigorousMock()
cordappProvider = rigorousMock()
doReturn(cordappProvider).whenever(services).cordappProvider
doReturn(testNetworkParameters()).whenever(services).networkParameters
}
@Test
@ -192,43 +200,190 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@Test
fun TransactionSignature() {
val metadata = SignatureMetadata(1, 1)
val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, metadata)
val signatureMetadata = SignatureMetadata(1, 1)
val partialMerkleTree = PartialMerkleTree(PartialTree.Node(
left = PartialTree.Leaf(SecureHash.randomSHA256()),
right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
))
val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, signatureMetadata, partialMerkleTree)
val json = mapper.valueToTree<ObjectNode>(transactionSignature)
val (bytes, by, signatureMetadata, partialMerkleTree) = json.assertHasOnlyFields(
val (bytesJson, byJson, signatureMetadataJson, partialMerkleTreeJson) = json.assertHasOnlyFields(
"bytes",
"by",
"signatureMetadata",
"partialMerkleTree"
)
assertThat(bytes.binaryValue()).isEqualTo(transactionSignature.bytes)
assertThat(by.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
assertThat(signatureMetadata.valueAs<SignatureMetadata>(mapper)).isEqualTo(metadata)
assertThat(partialMerkleTree.isNull).isTrue()
assertThat(bytesJson.binaryValue()).isEqualTo(transactionSignature.bytes)
assertThat(byJson.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
assertThat(signatureMetadataJson.valueAs<SignatureMetadata>(mapper)).isEqualTo(signatureMetadata)
assertThat(partialMerkleTreeJson.valueAs<PartialMerkleTree>(mapper).root).isEqualTo(partialMerkleTree.root)
assertThat(mapper.convertValue<TransactionSignature>(json)).isEqualTo(transactionSignature)
}
// TODO Add test for PartialMerkleTree
@Test
fun SignedTransaction() {
val attachmentRef = SecureHash.randomSHA256()
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
doReturn(testNetworkParameters()).whenever(services).networkParameters
val stx = makeDummyStx()
fun `SignedTransaction (WireTransaction)`() {
val attachmentId = SecureHash.randomSHA256()
doReturn(attachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
val wtx = TransactionBuilder(
notary = DUMMY_NOTARY,
inputs = mutableListOf(StateRef(SecureHash.randomSHA256(), 1)),
attachments = mutableListOf(attachmentId),
outputs = mutableListOf(createTransactionState()),
commands = mutableListOf(Command(DummyCommandData, listOf(BOB_PUBKEY))),
window = TimeWindow.fromStartAndDuration(Instant.now(), 1.hours),
privacySalt = net.corda.core.contracts.PrivacySalt()
).toWireTransaction(services)
val stx = sign(wtx)
partyObjectMapper.identities += listOf(MINI_CORP.party, DUMMY_NOTARY)
val json = mapper.valueToTree<ObjectNode>(stx)
println(mapper.writeValueAsString(json))
val (txBits, signatures) = json.assertHasOnlyFields("txBits", "signatures")
assertThat(txBits.binaryValue()).isEqualTo(stx.txBits.bytes)
val sigs = signatures.elements().asSequence().map { it.valueAs<TransactionSignature>(mapper) }.toList()
assertThat(sigs).isEqualTo(stx.sigs)
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs)
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "privacySalt")
assertThat(wtxFields[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs)
assertThat(wtxFields[3].childrenAs<SecureHash>(mapper)).isEqualTo(wtx.attachments)
assertThat(wtxFields[4].childrenAs<TransactionState<*>>(mapper)).isEqualTo(wtx.outputs)
assertThat(wtxFields[5].childrenAs<Command<*>>(mapper)).isEqualTo(wtx.commands)
assertThat(wtxFields[6].valueAs<TimeWindow>(mapper)).isEqualTo(wtx.timeWindow)
assertThat(wtxFields[7].valueAs<PrivacySalt>(mapper)).isEqualTo(wtx.privacySalt)
assertThat(mapper.convertValue<WireTransaction>(wtxJson)).isEqualTo(wtx)
assertThat(mapper.convertValue<SignedTransaction>(json)).isEqualTo(stx)
}
@Test
fun TransactionState() {
val txState = createTransactionState()
val json = mapper.valueToTree<ObjectNode>(txState)
println(mapper.writeValueAsString(json))
partyObjectMapper.identities += listOf(MINI_CORP.party, DUMMY_NOTARY)
assertThat(mapper.convertValue<TransactionState<*>>(json)).isEqualTo(txState)
}
@Test
fun Command() {
val command = Command(DummyCommandData, listOf(BOB_PUBKEY))
val json = mapper.valueToTree<ObjectNode>(command)
assertThat(mapper.convertValue<Command<*>>(json)).isEqualTo(command)
}
@Test
fun `TimeWindow - fromOnly`() {
val fromOnly = TimeWindow.fromOnly(Instant.now())
val json = mapper.valueToTree<ObjectNode>(fromOnly)
assertThat(mapper.convertValue<TimeWindow>(json)).isEqualTo(fromOnly)
}
@Test
fun `TimeWindow - untilOnly`() {
val untilOnly = TimeWindow.untilOnly(Instant.now())
val json = mapper.valueToTree<ObjectNode>(untilOnly)
assertThat(mapper.convertValue<TimeWindow>(json)).isEqualTo(untilOnly)
}
@Test
fun `TimeWindow - between`() {
val between = TimeWindow.between(Instant.now(), Instant.now() + 1.days)
val json = mapper.valueToTree<ObjectNode>(between)
assertThat(mapper.convertValue<TimeWindow>(json)).isEqualTo(between)
}
@Test
fun PrivacySalt() {
val privacySalt = net.corda.core.contracts.PrivacySalt()
val json = mapper.valueToTree<TextNode>(privacySalt)
assertThat(json.textValue()).isEqualTo(privacySalt.bytes.toHexString())
assertThat(mapper.convertValue<PrivacySalt>(json)).isEqualTo(privacySalt)
}
@Test
fun SignatureMetadata() {
val signatureMetadata = SignatureMetadata(2, Crypto.ECDSA_SECP256R1_SHA256.schemeNumberID)
val json = mapper.valueToTree<ObjectNode>(signatureMetadata)
val (platformVersion, scheme) = json.assertHasOnlyFields("platformVersion", "scheme")
assertThat(platformVersion.intValue()).isEqualTo(2)
assertThat(scheme.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256")
assertThat(mapper.convertValue<SignatureMetadata>(json)).isEqualTo(signatureMetadata)
}
@Test
fun `SignatureMetadata on unknown schemeNumberID`() {
val signatureMetadata = SignatureMetadata(2, Int.MAX_VALUE)
val json = mapper.valueToTree<ObjectNode>(signatureMetadata)
assertThat(json["scheme"].intValue()).isEqualTo(Int.MAX_VALUE)
assertThat(mapper.convertValue<SignatureMetadata>(json)).isEqualTo(signatureMetadata)
}
@Test
fun `SignatureScheme serialization`() {
val json = mapper.valueToTree<TextNode>(Crypto.ECDSA_SECP256R1_SHA256)
assertThat(json.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256")
}
@Test
fun `SignatureScheme deserialization`() {
assertThat(mapper.convertValue<SignatureScheme>(TextNode("EDDSA_ED25519_SHA512"))).isSameAs(Crypto.EDDSA_ED25519_SHA512)
assertThat(mapper.convertValue<SignatureScheme>(IntNode(4))).isSameAs(Crypto.EDDSA_ED25519_SHA512)
}
@Test
fun `PartialTree IncludedLeaf`() {
val includedLeaf = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
val json = mapper.valueToTree<ObjectNode>(includedLeaf)
assertThat(json.assertHasOnlyFields("includedLeaf")[0].textValue()).isEqualTo(includedLeaf.hash.toString())
assertThat(mapper.convertValue<PartialTree.IncludedLeaf>(json)).isEqualTo(includedLeaf)
}
@Test
fun `PartialTree Leaf`() {
val leaf = PartialTree.Leaf(SecureHash.randomSHA256())
val json = mapper.valueToTree<ObjectNode>(leaf)
assertThat(json.assertHasOnlyFields("leaf")[0].textValue()).isEqualTo(leaf.hash.toString())
assertThat(mapper.convertValue<PartialTree.Leaf>(json)).isEqualTo(leaf)
}
@Test
fun `simple PartialTree Node`() {
val node = PartialTree.Node(
left = PartialTree.Leaf(SecureHash.randomSHA256()),
right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
)
val json = mapper.valueToTree<ObjectNode>(node)
println(mapper.writeValueAsString(json))
val (leftJson, rightJson) = json.assertHasOnlyFields("left", "right")
assertThat(leftJson.valueAs<PartialTree>(mapper)).isEqualTo(node.left)
assertThat(rightJson.valueAs<PartialTree>(mapper)).isEqualTo(node.right)
assertThat(mapper.convertValue<PartialTree.Node>(json)).isEqualTo(node)
}
@Test
fun `complex PartialTree Node`() {
val node = PartialTree.Node(
left = PartialTree.IncludedLeaf(SecureHash.randomSHA256()),
right = PartialTree.Node(
left = PartialTree.Leaf(SecureHash.randomSHA256()),
right = PartialTree.Leaf(SecureHash.randomSHA256())
)
)
val json = mapper.valueToTree<ObjectNode>(node)
println(mapper.writeValueAsString(json))
assertThat(mapper.convertValue<PartialTree.Node>(json)).isEqualTo(node)
}
// TODO Issued
// TODO PartyAndReference
@Test
fun CordaX500Name() {
testToStringSerialisation(CordaX500Name(commonName = "COMMON", organisationUnit = "ORG UNIT", organisation = "ORG", locality = "NYC", state = "NY", country = "US"))
testToStringSerialisation(CordaX500Name(
commonName = "COMMON",
organisationUnit = "ORG UNIT",
organisation = "ORG",
locality = "NYC",
state = "NY",
country = "US"
))
}
@Test
@ -472,14 +627,36 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
assertThat(mapper.convertValue<NonCtorPropertiesData>(json)).isEqualTo(data)
}
private fun makeDummyStx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services)
@Test
fun `kotlin object`() {
val json = mapper.valueToTree<ObjectNode>(KotlinObject)
assertThat(mapper.convertValue<KotlinObject>(json)).isSameAs(KotlinObject)
}
@Test
fun `@CordaSerializable kotlin object`() {
val json = mapper.valueToTree<ObjectNode>(CordaSerializableKotlinObject)
assertThat(mapper.convertValue<CordaSerializableKotlinObject>(json)).isSameAs(CordaSerializableKotlinObject)
}
private fun sign(ctx: CoreTransaction): SignedTransaction {
val partialMerkleTree = PartialMerkleTree(PartialTree.Node(
left = PartialTree.Leaf(SecureHash.randomSHA256()),
right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
))
val signatures = listOf(
TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID)),
TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID), partialMerkleTree),
TransactionSignature(ByteArray(1), BOB_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(BOB_PUBKEY).schemeNumberID))
)
return SignedTransaction(wtx, signatures)
return SignedTransaction(ctx, signatures)
}
private fun createTransactionState(): TransactionState<DummyContract.SingleOwnerState> {
return TransactionState(
data = DummyContract.SingleOwnerState(magicNumber = 123, owner = MINI_CORP.party),
contract = DummyContract.PROGRAM_ID,
notary = DUMMY_NOTARY
)
}
private inline fun <reified T : Any> testToStringSerialisation(value: T) {
@ -505,6 +682,11 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
val nonCtor: Int get() = value
}
private object KotlinObject
@CordaSerializable
private object CordaSerializableKotlinObject
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
override var isFullParties: Boolean = false
val identities = ArrayList<Party>()

View File

@ -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.
* In case the input is not a key in the supportedSignatureSchemes map, null will be returned.
* @param schemeCodeName a [String] that should match a supported signature scheme code name (e.g. ECDSA_SECP256K1_SHA256), see [Crypto].

View File

@ -16,8 +16,9 @@ import java.security.spec.AlgorithmParameterSpec
/**
* This class is used to define a digital signature scheme.
* @param schemeNumberID we assign a number ID for better efficiency on-wire serialisation. Please ensure uniqueness between schemes.
* @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
* @param schemeNumberID unique number ID for better efficiency on-wire serialisation.
* @param schemeCodeName unique code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256,
* EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
* @param signatureOID ASN.1 algorithm identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA)
* @param alternativeOIDs ASN.1 algorithm identifiers for keys of the signature, where we want to map multiple keys to
* the same signature scheme.

View File

@ -17,12 +17,7 @@ import com.google.common.hash.HashingInputStream
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig
import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sha256
import net.corda.core.crypto.sign
import net.corda.core.crypto.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext
@ -47,6 +42,7 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Modifier
import java.math.BigDecimal
import java.net.HttpURLConnection
@ -61,23 +57,11 @@ import java.nio.file.Paths
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.CertPathValidator
import java.security.cert.CertPathValidatorException
import java.security.cert.PKIXCertPathValidatorResult
import java.security.cert.PKIXParameters
import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
import java.security.cert.*
import java.time.Duration
import java.time.temporal.Temporal
import java.util.*
import java.util.Spliterator.DISTINCT
import java.util.Spliterator.IMMUTABLE
import java.util.Spliterator.NONNULL
import java.util.Spliterator.ORDERED
import java.util.Spliterator.SIZED
import java.util.Spliterator.SORTED
import java.util.Spliterator.SUBSIZED
import java.util.Spliterator.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit
import java.util.stream.IntStream
@ -321,6 +305,23 @@ fun <T : Any> KClass<T>.objectOrNewInstance(): T {
return this.objectInstance ?: this.createInstance()
}
/** Similar to [KClass.objectInstance] but also works on private objects. */
val <T : Any> Class<T>.kotlinObjectInstance: T? get() {
return try {
kotlin.objectInstance
} catch (_: Throwable) {
val field = try { getDeclaredField("INSTANCE") } catch (_: NoSuchFieldException) { null }
field?.let {
if (it.type == this && it.isPublic && it.isStatic && it.isFinal) {
it.isAccessible = true
uncheckedCast(it.get(null))
} else {
null
}
}
}
}
/**
* A simple wrapper around a [Field] object providing type safe read and write access using [value], ignoring the field's
* visibility.
@ -395,6 +396,12 @@ inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifie
inline val Class<*>.isConcreteClass: Boolean get() = !isInterface && !isAbstractClass
inline val Member.isPublic: Boolean get() = Modifier.isPublic(modifiers)
inline val Member.isStatic: Boolean get() = Modifier.isStatic(modifiers)
inline val Member.isFinal: Boolean get() = Modifier.isFinal(modifiers)
fun URI.toPath(): Path = Paths.get(this)
fun URL.toPath(): Path = toURI().toPath()

View File

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

View File

@ -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.
* 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.
* 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``
back.
* ``CertPath`` objects are serialised as a list of ``X509Certificate`` objects.
* ``SignedTransaction`` is serialised into its ``txBits`` and ``signatures`` and can be deserialised back
* ``WireTransaction`` now nicely outputs into its components: ``id``, ``notary``, ``inputs``, ``attachments``, ``outputs``,
``commands``, ``timeWindow`` and ``privacySalt``. This can be deserialised back.
* ``SignedTransaction`` is serialised into ``wire`` (i.e. currently only ``WireTransaction`` tested) and ``signatures``,
and can be deserialised back.
* ``fullParties`` boolean parameter added to ``JacksonSupport.createDefaultMapper`` and ``createNonRpcMapper``. If ``true``
then ``Party`` objects are serialised as JSON objects with the ``name`` and ``owningKey`` fields. For ``PartyAndCertificate``

View File

@ -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).
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 |
@ -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 | |
| | 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 | | |
| | | future. SPHINCS is based ultimately on a clever usage of | | |
| | | Merkle hash trees. Hash functions are a very heavily | | |

View File

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

View File

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

View File

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

View File

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

View File

@ -52,10 +52,6 @@ enterpriseConfiguration = {
maximumMessagingBatchSize = 256
p2pConfirmationWindowSize = 1048576
brokerConnectionTtlCheckIntervalMs = 20
stateMachine = {
eventQueueSize = 16
sessionDeliverPersistenceStrategy = "OnNextCommit"
}
}
useMultiThreadedSMM = true
}

View File

@ -16,6 +16,7 @@ import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.Util
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.writer
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
@ -25,7 +26,6 @@ import net.corda.serialization.internal.MutableClassWhitelist
import net.corda.serialization.internal.TransientClassWhiteList
import net.corda.serialization.internal.amqp.hasCordaSerializable
import java.io.PrintWriter
import java.lang.reflect.Modifier
import java.lang.reflect.Modifier.isAbstract
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.Paths
@ -85,22 +85,7 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl
override fun registerImplicit(type: Class<*>): Registration {
val targetType = typeForSerializationOf(type)
// Is this a Kotlin object? We use our own reflection here rather than .kotlin.objectInstance because Kotlin
// reflection won't work for private objects, and can throw exceptions in other circumstances as well.
val objectInstance = try {
targetType.declaredFields.singleOrNull {
it.name == "INSTANCE" &&
it.type == type &&
Modifier.isStatic(it.modifiers) &&
Modifier.isFinal(it.modifiers) &&
Modifier.isPublic(it.modifiers)
}?.let {
it.isAccessible = true
type.cast(it.get(null)!!)
}
} catch (t: Throwable) {
null
}
val objectInstance = targetType.kotlinObjectInstance
// We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent.
val references = kryo.references

View File

@ -10,7 +10,6 @@
package net.corda.node.services.config
import net.corda.node.services.statemachine.transitions.StateMachineConfiguration
import java.net.InetAddress
data class EnterpriseConfiguration(
@ -46,8 +45,7 @@ data class PerformanceTuning(
val maximumMessagingBatchSize: Int,
val rpcThreadPoolSize: Int,
val p2pConfirmationWindowSize: Int,
val brokerConnectionTtlCheckIntervalMs: Long,
val stateMachine: StateMachineConfiguration
val brokerConnectionTtlCheckIntervalMs: Long
) {
companion object {
val default = PerformanceTuning(
@ -55,8 +53,7 @@ data class PerformanceTuning(
maximumMessagingBatchSize = 256,
rpcThreadPoolSize = 4,
p2pConfirmationWindowSize = 1048576,
brokerConnectionTtlCheckIntervalMs = 20,
stateMachine = StateMachineConfiguration.default
brokerConnectionTtlCheckIntervalMs = 20
)
}
}

View File

@ -23,20 +23,12 @@ import net.corda.core.flows.FlowInfo
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.Party
import net.corda.core.internal.ConcurrentBox
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.*
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed
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.serialization.*
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.Try
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.messaging.DeduplicationHandler
import net.corda.node.services.messaging.ReceivedMessage
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.MetricInterceptor
import net.corda.node.services.statemachine.interceptors.PrintingInterceptor
import net.corda.node.services.statemachine.interceptors.*
import net.corda.node.services.statemachine.transitions.StateMachine
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -299,8 +286,6 @@ class MultiThreadedStateMachineManager(
}
}
private val stateMachineConfiguration = serviceHub.configuration.enterpriseConfiguration.tuning.stateMachine
private fun checkQuasarJavaAgentPresence() {
check(SuspendableHelper.isJavaAgentActive(), {
"""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,
transitionExecutor = transitionExecutor,
actionExecutor = actionExecutor!!,
stateMachine = StateMachine(id, stateMachineConfiguration, secureRandom),
stateMachine = StateMachine(id, secureRandom),
serviceHub = serviceHub,
checkpointSerializationContext = checkpointSerializationContext!!
)

View File

@ -31,7 +31,11 @@ import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
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.Try
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.messaging.DeduplicationHandler
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.StateMachineConfiguration
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
@ -287,8 +294,6 @@ class SingleThreadedStateMachineManager(
}
}
private val stateMachineConfiguration = serviceHub.configuration.enterpriseConfiguration.tuning.stateMachine
private fun checkQuasarJavaAgentPresence() {
check(SuspendableHelper.isJavaAgentActive(), {
"""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,
transitionExecutor = transitionExecutor,
actionExecutor = actionExecutor!!,
stateMachine = StateMachine(id, stateMachineConfiguration, secureRandom),
stateMachine = StateMachine(id, secureRandom),
serviceHub = serviceHub,
checkpointSerializationContext = checkpointSerializationContext!!
)

View File

@ -10,57 +10,16 @@
package net.corda.node.services.statemachine.transitions
import net.corda.core.flows.*
import net.corda.node.services.statemachine.*
import net.corda.core.flows.StateMachineRunId
import net.corda.node.services.statemachine.Event
import net.corda.node.services.statemachine.StateMachineState
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(
val id: StateMachineRunId,
val configuration: StateMachineConfiguration,
val secureRandom: SecureRandom
) {
fun transition(event: Event, state: StateMachineState): TransitionResult {
return TopLevelTransition(TransitionContext(id, configuration, secureRandom), state, event).transition()
return TopLevelTransition(TransitionContext(id, secureRandom), state, event).transition()
}
}

View File

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

View File

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

View File

@ -37,10 +37,6 @@ enterpriseConfiguration = {
maximumMessagingBatchSize = 256
p2pConfirmationWindowSize = 1048576
brokerConnectionTtlCheckIntervalMs = 20
stateMachine = {
eventQueueSize = 16
sessionDeliverPersistenceStrategy = "OnNextCommit"
}
}
useMultiThreadedSMM = true
}

View File

@ -41,10 +41,6 @@ enterpriseConfiguration = {
maximumMessagingBatchSize = 256
p2pConfirmationWindowSize = 1048576
brokerConnectionTtlCheckIntervalMs = 20
stateMachine = {
eventQueueSize = 16
sessionDeliverPersistenceStrategy = "OnNextCommit"
}
}
useMultiThreadedSMM = true
}

View File

@ -12,6 +12,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.hash.Hasher
import com.google.common.hash.Hashing
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toBase64
import java.io.NotSerializableException
@ -163,7 +164,7 @@ class SerializerFingerPrinter : FingerPrinter {
}.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH)
} else {
hasher.fingerprintWithCustomSerializerOrElse(factory!!, type, type) {
if (type.objectInstance() != null) {
if (type.kotlinObjectInstance != null) {
// TODO: name collision is too likely for kotlin objects, we need to introduce some reference
// to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name)

View File

@ -13,6 +13,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeToken
import net.corda.core.internal.isConcreteClass
import net.corda.core.internal.isPublic
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
@ -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
// parameter will be considered
clazz!!.declaredMethods?.map { func ->
if (!Modifier.isPublic(func.modifiers)) return@map
if (!func.isPublic) return@map
if (func.name == "getClass") return@map
PropertyDescriptorsRegex.re.find(func.name)?.apply {
@ -547,49 +548,3 @@ fun hasCordaSerializable(type: Class<*>): Boolean {
|| type.interfaces.any(::hasCordaSerializable)
|| (type.superclass != null && hasCordaSerializable(type.superclass))
}
/**
* By default use Kotlin reflection and grab the objectInstance. However, that doesn't play nicely with nested
* private objects. Even setting the accessibility override (setAccessible) still causes an
* [IllegalAccessException] when attempting to retrieve the value of the INSTANCE field.
*
* Whichever reference to the class Kotlin reflection uses, override (set from setAccessible) on that field
* isn't set even when it was explicitly set as accessible before calling into the kotlin reflection routines.
*
* For example
*
* clazz.getDeclaredField("INSTANCE")?.apply {
* isAccessible = true
* kotlin.objectInstance // This throws as the INSTANCE field isn't accessible
* }
*
* Therefore default back to good old java reflection and simply look for the INSTANCE field as we are never going
* to serialize a companion object.
*
* As such, if objectInstance fails access, revert to Java reflection and try that
*/
fun Class<*>.objectInstance(): Any? {
return try {
this.kotlin.objectInstance
} catch (e: IllegalAccessException) {
// Check it really is an object (i.e. it has no constructor)
if (constructors.isNotEmpty()) null
else {
try {
this.getDeclaredField("INSTANCE")?.let { field ->
// and must be marked as both static and final (>0 means they're set)
if (modifiers and Modifier.STATIC == 0 || modifiers and Modifier.FINAL == 0) null
else {
val accessibility = field.isAccessible
field.isAccessible = true
val obj = field.get(null)
field.isAccessible = accessibility
obj
}
}
} catch (e: NoSuchFieldException) {
null
}
}
}
}

View File

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