mirror of
https://github.com/corda/corda.git
synced 2025-06-19 23:53:52 +00:00
CORDA-540: Make Verifier work in AMQP mode (#1870)
This commit is contained in:
@ -2435,6 +2435,17 @@ public final class net.corda.core.serialization.MissingAttachmentsException exte
|
|||||||
public <init>(List)
|
public <init>(List)
|
||||||
@org.jetbrains.annotations.NotNull public final List getIds()
|
@org.jetbrains.annotations.NotNull public final List getIds()
|
||||||
##
|
##
|
||||||
|
public final class net.corda.core.serialization.ObjectWithCompatibleContext extends java.lang.Object
|
||||||
|
public <init>(Object, net.corda.core.serialization.SerializationContext)
|
||||||
|
@org.jetbrains.annotations.NotNull public final Object component1()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext component2()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.ObjectWithCompatibleContext copy(Object, net.corda.core.serialization.SerializationContext)
|
||||||
|
public boolean equals(Object)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getContext()
|
||||||
|
@org.jetbrains.annotations.NotNull public final Object getObj()
|
||||||
|
public int hashCode()
|
||||||
|
public String toString()
|
||||||
|
##
|
||||||
public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object
|
public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object
|
||||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext)
|
@org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext)
|
||||||
##
|
##
|
||||||
@ -2476,6 +2487,7 @@ public abstract class net.corda.core.serialization.SerializationFactory extends
|
|||||||
public <init>()
|
public <init>()
|
||||||
public final Object asCurrent(kotlin.jvm.functions.Function1)
|
public final Object asCurrent(kotlin.jvm.functions.Function1)
|
||||||
@org.jetbrains.annotations.NotNull public abstract Object deserialize(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext)
|
@org.jetbrains.annotations.NotNull public abstract Object deserialize(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext)
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ObjectWithCompatibleContext deserializeWithCompatibleContext(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext)
|
||||||
@org.jetbrains.annotations.Nullable public final net.corda.core.serialization.SerializationContext getCurrentContext()
|
@org.jetbrains.annotations.Nullable public final net.corda.core.serialization.SerializationContext getCurrentContext()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getDefaultContext()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getDefaultContext()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationContext)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationContext)
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.core.contracts
|
|||||||
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.extractFile
|
import net.corda.core.internal.extractFile
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -17,6 +18,7 @@ import java.util.jar.JarInputStream
|
|||||||
* - Legal documents
|
* - Legal documents
|
||||||
* - Facts generated by oracles which might be reused a lot
|
* - Facts generated by oracles which might be reused a lot
|
||||||
*/
|
*/
|
||||||
|
@CordaSerializable
|
||||||
interface Attachment : NamedByHash {
|
interface Attachment : NamedByHash {
|
||||||
fun open(): InputStream
|
fun open(): InputStream
|
||||||
fun openAsJAR(): JarInputStream {
|
fun openAsJAR(): JarInputStream {
|
||||||
|
@ -19,7 +19,7 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str
|
|||||||
class ContractConstraintRejection(txId: SecureHash, contractClass: String)
|
class ContractConstraintRejection(txId: SecureHash, contractClass: String)
|
||||||
: TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null)
|
: TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null)
|
||||||
|
|
||||||
class MissingAttachmentRejection(txId: SecureHash, contractClass: String)
|
class MissingAttachmentRejection(txId: SecureHash, val contractClass: String)
|
||||||
: TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null)
|
: TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null)
|
||||||
|
|
||||||
class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable)
|
class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable)
|
||||||
|
@ -8,6 +8,8 @@ import net.corda.core.utilities.OpaqueBytes
|
|||||||
import net.corda.core.utilities.sequence
|
import net.corda.core.utilities.sequence
|
||||||
import java.sql.Blob
|
import java.sql.Blob
|
||||||
|
|
||||||
|
data class ObjectWithCompatibleContext<out T : Any>(val obj: T, val context: SerializationContext)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstraction for serializing and deserializing objects, with support for versioning of the wire format via
|
* An abstraction for serializing and deserializing objects, with support for versioning of the wire format via
|
||||||
* a header / prefix in the bytes.
|
* a header / prefix in the bytes.
|
||||||
@ -22,6 +24,16 @@ abstract class SerializationFactory {
|
|||||||
*/
|
*/
|
||||||
abstract fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T
|
abstract fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize the bytes in to an object, using the prefixed bytes to determine the format.
|
||||||
|
*
|
||||||
|
* @param byteSequence The bytes to deserialize, including a format header prefix.
|
||||||
|
* @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown.
|
||||||
|
* @param context A context that configures various parameters to deserialization.
|
||||||
|
* @return deserialized object along with [SerializationContext] to identify encoding used.
|
||||||
|
*/
|
||||||
|
abstract fun <T : Any> deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): ObjectWithCompatibleContext<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize an object to bytes using the preferred serialization format version from the context.
|
* Serialize an object to bytes using the preferred serialization format version from the context.
|
||||||
*
|
*
|
||||||
@ -87,6 +99,8 @@ abstract class SerializationFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typealias VersionHeader = ByteSequence
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameters to serialization and deserialization.
|
* Parameters to serialization and deserialization.
|
||||||
*/
|
*/
|
||||||
@ -94,7 +108,7 @@ interface SerializationContext {
|
|||||||
/**
|
/**
|
||||||
* When serializing, use the format this header sequence represents.
|
* When serializing, use the format this header sequence represents.
|
||||||
*/
|
*/
|
||||||
val preferredSerializationVersion: ByteSequence
|
val preferredSerializationVersion: VersionHeader
|
||||||
/**
|
/**
|
||||||
* The class loader to use for deserialization.
|
* The class loader to use for deserialization.
|
||||||
*/
|
*/
|
||||||
@ -147,7 +161,7 @@ interface SerializationContext {
|
|||||||
/**
|
/**
|
||||||
* Helper method to return a new context based on this context but with serialization using the format this header sequence represents.
|
* Helper method to return a new context based on this context but with serialization using the format this header sequence represents.
|
||||||
*/
|
*/
|
||||||
fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext
|
fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The use case that we are serializing for, since it influences the implementations chosen.
|
* The use case that we are serializing for, since it influences the implementations chosen.
|
||||||
@ -174,6 +188,15 @@ inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: Seri
|
|||||||
return serializationFactory.deserialize(this, T::class.java, context)
|
return serializationFactory.deserialize(this, T::class.java, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additionally returns [SerializationContext] which was used for encoding.
|
||||||
|
* It might be helpful to know [SerializationContext] to use the same encoding in the reply.
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||||
|
context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext<T> {
|
||||||
|
return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
|
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
|
||||||
*/
|
*/
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package net.corda.nodeapi
|
package net.corda.nodeapi
|
||||||
|
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.serialization.serialize
|
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
|
import net.corda.core.utilities.sequence
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
||||||
import org.apache.activemq.artemis.reader.MessageUtil
|
import org.apache.activemq.artemis.reader.MessageUtil
|
||||||
@ -20,12 +20,15 @@ object VerifierApi {
|
|||||||
val responseAddress: SimpleString
|
val responseAddress: SimpleString
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromClientMessage(message: ClientMessage): VerificationRequest {
|
fun fromClientMessage(message: ClientMessage): ObjectWithCompatibleContext<VerificationRequest> {
|
||||||
return VerificationRequest(
|
val bytes = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }
|
||||||
|
val bytesSequence = bytes.sequence()
|
||||||
|
val (transaction, context) = bytesSequence.deserializeWithCompatibleContext<LedgerTransaction>()
|
||||||
|
val request = VerificationRequest(
|
||||||
message.getLongProperty(VERIFICATION_ID_FIELD_NAME),
|
message.getLongProperty(VERIFICATION_ID_FIELD_NAME),
|
||||||
ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }.deserialize(),
|
transaction,
|
||||||
MessageUtil.getJMSReplyTo(message)
|
MessageUtil.getJMSReplyTo(message))
|
||||||
)
|
return ObjectWithCompatibleContext(request, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,10 +52,10 @@ object VerifierApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun writeToClientMessage(message: ClientMessage) {
|
fun writeToClientMessage(message: ClientMessage, context: SerializationContext) {
|
||||||
message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId)
|
message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId)
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize().bytes)
|
message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize(context = context).bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@ package net.corda.nodeapi.internal.serialization
|
|||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.internal.AbstractAttachment
|
import net.corda.core.internal.AbstractAttachment
|
||||||
|
|
||||||
class GeneratedAttachment(bytes: ByteArray) : AbstractAttachment({ bytes }) {
|
class GeneratedAttachment(val bytes: ByteArray) : AbstractAttachment({ bytes }) {
|
||||||
override val id = bytes.sha256()
|
override val id = bytes.sha256()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import net.corda.core.serialization.*
|
|||||||
import net.corda.core.utilities.ByteSequence
|
import net.corda.core.utilities.ByteSequence
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -37,7 +38,7 @@ object NotSupportedSerializationScheme : SerializationScheme {
|
|||||||
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> = doThrow()
|
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> = doThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SerializationContextImpl(override val preferredSerializationVersion: ByteSequence,
|
data class SerializationContextImpl(override val preferredSerializationVersion: VersionHeader,
|
||||||
override val deserializationClassLoader: ClassLoader,
|
override val deserializationClassLoader: ClassLoader,
|
||||||
override val whitelist: ClassWhitelist,
|
override val whitelist: ClassWhitelist,
|
||||||
override val properties: Map<Any, Any>,
|
override val properties: Map<Any, Any>,
|
||||||
@ -88,36 +89,54 @@ data class SerializationContextImpl(override val preferredSerializationVersion:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun withPreferredSerializationVersion(versionHeader: ByteSequence) = copy(preferredSerializationVersion = versionHeader)
|
override fun withPreferredSerializationVersion(versionHeader: VersionHeader) = copy(preferredSerializationVersion = versionHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val HEADER_SIZE: Int = 8
|
private const val HEADER_SIZE: Int = 8
|
||||||
|
|
||||||
|
fun ByteSequence.obtainHeaderSignature(): VersionHeader = take(HEADER_SIZE).copy()
|
||||||
|
|
||||||
open class SerializationFactoryImpl : SerializationFactory() {
|
open class SerializationFactoryImpl : SerializationFactory() {
|
||||||
private val creator: List<StackTraceElement> = Exception().stackTrace.asList()
|
private val creator: List<StackTraceElement> = Exception().stackTrace.asList()
|
||||||
|
|
||||||
private val registeredSchemes: MutableCollection<SerializationScheme> = Collections.synchronizedCollection(mutableListOf())
|
private val registeredSchemes: MutableCollection<SerializationScheme> = Collections.synchronizedCollection(mutableListOf())
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
// TODO: This is read-mostly. Probably a faster implementation to be found.
|
// TODO: This is read-mostly. Probably a faster implementation to be found.
|
||||||
private val schemes: ConcurrentHashMap<Pair<ByteSequence, SerializationContext.UseCase>, SerializationScheme> = ConcurrentHashMap()
|
private val schemes: ConcurrentHashMap<Pair<ByteSequence, SerializationContext.UseCase>, SerializationScheme> = ConcurrentHashMap()
|
||||||
|
|
||||||
private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme {
|
private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): Pair<SerializationScheme, VersionHeader> {
|
||||||
// truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays
|
// truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays
|
||||||
return schemes.computeIfAbsent(byteSequence.take(HEADER_SIZE).copy() to target) {
|
val lookupKey = byteSequence.obtainHeaderSignature() to target
|
||||||
|
val scheme = schemes.computeIfAbsent(lookupKey) {
|
||||||
registeredSchemes
|
registeredSchemes
|
||||||
.filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) }
|
.filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) }
|
||||||
.forEach { return@computeIfAbsent it }
|
.forEach { return@computeIfAbsent it }
|
||||||
|
logger.warn("Cannot find serialization scheme for: $lookupKey, registeredSchemes are: $registeredSchemes")
|
||||||
NotSupportedSerializationScheme
|
NotSupportedSerializationScheme
|
||||||
}
|
}
|
||||||
|
return scheme to lookupKey.first
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
|
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
|
||||||
return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).deserialize(byteSequence, clazz, context) } }
|
return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).first.deserialize(byteSequence, clazz, context) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(NotSerializableException::class)
|
||||||
|
override fun <T : Any> deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): ObjectWithCompatibleContext<T> {
|
||||||
|
return asCurrent {
|
||||||
|
withCurrentContext(context) {
|
||||||
|
val (scheme, versionHeader) = schemeFor(byteSequence, context.useCase)
|
||||||
|
val deserializedObject = scheme.deserialize(byteSequence, clazz, context)
|
||||||
|
ObjectWithCompatibleContext(deserializedObject, context.withPreferredSerializationVersion(versionHeader))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
|
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
|
||||||
return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).serialize(obj, context) } }
|
return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).first.serialize(obj, context) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerScheme(scheme: SerializationScheme) {
|
fun registerScheme(scheme: SerializationScheme) {
|
||||||
|
@ -10,7 +10,6 @@ import com.nhaarman.mockito_kotlin.verify
|
|||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.utilities.ByteSequence
|
|
||||||
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
||||||
import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
|
import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
|
||||||
import net.corda.testing.node.MockAttachmentStorage
|
import net.corda.testing.node.MockAttachmentStorage
|
||||||
@ -108,16 +107,6 @@ class CordaClassResolverTests {
|
|||||||
val emptyMapClass = mapOf<Any, Any>().javaClass
|
val emptyMapClass = mapOf<Any, Any>().javaClass
|
||||||
}
|
}
|
||||||
|
|
||||||
val factory: SerializationFactory = object : SerializationFactory() {
|
|
||||||
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P)
|
private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P)
|
||||||
private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P)
|
private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P)
|
||||||
|
|
||||||
|
@ -103,6 +103,10 @@ class TestSerializationFactory : SerializationFactory() {
|
|||||||
return delegate!!.deserialize(byteSequence, clazz, context)
|
return delegate!!.deserialize(byteSequence, clazz, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun <T : Any> deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): ObjectWithCompatibleContext<T> {
|
||||||
|
return delegate!!.deserializeWithCompatibleContext(byteSequence, clazz, context)
|
||||||
|
}
|
||||||
|
|
||||||
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
|
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
|
||||||
return delegate!!.serialize(obj, context)
|
return delegate!!.serialize(obj, context)
|
||||||
}
|
}
|
||||||
@ -147,7 +151,7 @@ class TestSerializationContext : SerializationContext {
|
|||||||
return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withWhitelisted(clazz) }
|
return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withWhitelisted(clazz) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext {
|
override fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext {
|
||||||
return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withPreferredSerializationVersion(versionHeader) }
|
return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withPreferredSerializationVersion(versionHeader) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.verifier
|
package net.corda.verifier
|
||||||
|
|
||||||
|
import net.corda.core.contracts.TransactionVerificationException
|
||||||
import net.corda.core.internal.concurrent.map
|
import net.corda.core.internal.concurrent.map
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
@ -18,6 +19,7 @@ import net.corda.testing.driver.NetworkMapStartStrategy
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.test.assertTrue
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class VerifierTests {
|
class VerifierTests {
|
||||||
@ -50,6 +52,21 @@ class VerifierTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `single verification fails`() {
|
||||||
|
verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) {
|
||||||
|
val aliceFuture = startVerificationRequestor(ALICE.name)
|
||||||
|
// Generate transactions as per usual, but then remove attachments making transaction invalid.
|
||||||
|
val transactions = generateTransactions(1).map { it.copy(attachments = emptyList()) }
|
||||||
|
val alice = aliceFuture.get()
|
||||||
|
startVerifier(alice)
|
||||||
|
alice.waitUntilNumberOfVerifiers(1)
|
||||||
|
val verificationRejection = transactions.map { alice.verifyTransaction(it) }.transpose().get().single()
|
||||||
|
assertTrue { verificationRejection is TransactionVerificationException.MissingAttachmentRejection}
|
||||||
|
assertTrue { verificationRejection!!.message!!.contains("Contract constraints failed, could not find attachment") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `multiple verifiers work with requestor`() {
|
fun `multiple verifiers work with requestor`() {
|
||||||
verifierDriver {
|
verifierDriver {
|
||||||
|
@ -17,17 +17,16 @@ import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME
|
|||||||
import net.corda.nodeapi.config.NodeSSLConfiguration
|
import net.corda.nodeapi.config.NodeSSLConfiguration
|
||||||
import net.corda.nodeapi.config.getValue
|
import net.corda.nodeapi.config.getValue
|
||||||
import net.corda.nodeapi.internal.addShutdownHook
|
import net.corda.nodeapi.internal.addShutdownHook
|
||||||
import net.corda.nodeapi.internal.serialization.AbstractKryoSerializationScheme
|
import net.corda.nodeapi.internal.serialization.*
|
||||||
import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
|
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
|
||||||
import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
data class VerifierConfiguration(
|
data class VerifierConfiguration(
|
||||||
override val baseDirectory: Path,
|
override val baseDirectory: Path,
|
||||||
val config: Config
|
val config: Config // NB: This property is being used via reflection.
|
||||||
) : NodeSSLConfiguration {
|
) : NodeSSLConfiguration {
|
||||||
val nodeHostAndPort: NetworkHostAndPort by config
|
val nodeHostAndPort: NetworkHostAndPort by config
|
||||||
override val keyStorePassword: String by config
|
override val keyStorePassword: String by config
|
||||||
@ -66,7 +65,7 @@ class Verifier {
|
|||||||
val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME)
|
val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME)
|
||||||
val replyProducer = session.createProducer()
|
val replyProducer = session.createProducer()
|
||||||
consumer.setMessageHandler {
|
consumer.setMessageHandler {
|
||||||
val request = VerifierApi.VerificationRequest.fromClientMessage(it)
|
val (request, context) = VerifierApi.VerificationRequest.fromClientMessage(it)
|
||||||
log.debug { "Received verification request with id ${request.verificationId}" }
|
log.debug { "Received verification request with id ${request.verificationId}" }
|
||||||
val error = try {
|
val error = try {
|
||||||
request.transaction.verify()
|
request.transaction.verify()
|
||||||
@ -77,7 +76,7 @@ class Verifier {
|
|||||||
}
|
}
|
||||||
val reply = session.createMessage(false)
|
val reply = session.createMessage(false)
|
||||||
val response = VerifierApi.VerificationResponse(request.verificationId, error)
|
val response = VerifierApi.VerificationResponse(request.verificationId, error)
|
||||||
response.writeToClientMessage(reply)
|
response.writeToClientMessage(reply, context)
|
||||||
replyProducer.send(request.responseAddress, reply)
|
replyProducer.send(request.responseAddress, reply)
|
||||||
it.acknowledge()
|
it.acknowledge()
|
||||||
}
|
}
|
||||||
@ -88,13 +87,18 @@ class Verifier {
|
|||||||
|
|
||||||
private fun initialiseSerialization() {
|
private fun initialiseSerialization() {
|
||||||
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
|
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
|
||||||
registerScheme(KryoVerifierSerializationScheme())
|
registerScheme(KryoVerifierSerializationScheme)
|
||||||
|
registerScheme(AMQPVerifierSerializationScheme)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Even though default context is set to Kryo P2P, the encoding will be adjusted depending on the incoming
|
||||||
|
* request received, see use of [context] in [main] method.
|
||||||
|
*/
|
||||||
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
|
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() {
|
private object KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() {
|
||||||
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||||
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
|
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
|
||||||
}
|
}
|
||||||
@ -102,4 +106,13 @@ class Verifier {
|
|||||||
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||||
|
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||||
|
return (byteSequence == AmqpHeaderV1_0 && (target == SerializationContext.UseCase.P2P))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException()
|
||||||
|
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user