mirror of
https://github.com/corda/corda.git
synced 2025-02-07 03:29:19 +00:00
Merge from O/S
This commit is contained in:
commit
3203bdae97
@ -4359,7 +4359,7 @@ public interface net.corda.core.schemas.QueryableState extends net.corda.core.co
|
||||
@NotNull
|
||||
public abstract Iterable<net.corda.core.schemas.MappedSchema> supportedSchemas()
|
||||
##
|
||||
public interface net.corda.core.schemas.StatePersistable extends java.io.Serializable
|
||||
public interface net.corda.core.schemas.StatePersistable
|
||||
##
|
||||
public interface net.corda.core.serialization.ClassWhitelist
|
||||
public abstract boolean hasListed(Class<?>)
|
||||
|
@ -157,10 +157,6 @@ allprojects {
|
||||
apply plugin: 'org.owasp.dependencycheck'
|
||||
apply plugin: 'kotlin-allopen'
|
||||
|
||||
// This line works around a serious performance regression in the Kotlin 1.2.50 gradle plugin, it's fixed in 1.2.51
|
||||
// TODO: Remove when we upgrade past Kotlin 1.2.51
|
||||
inspectClassesForKotlinIC.enabled = false
|
||||
|
||||
allOpen {
|
||||
annotations(
|
||||
"javax.persistence.Entity",
|
||||
|
@ -32,9 +32,6 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'java'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
@ -61,12 +58,13 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Add the top-level projects ONLY to the host project.
|
||||
runtime project.childProjects.values().collect {
|
||||
project(it.path)
|
||||
}
|
||||
configurations {
|
||||
runtime
|
||||
}
|
||||
|
||||
// Don't create an empty jar. The plugins are now in child projects.
|
||||
jar.enabled = false
|
||||
dependencies {
|
||||
// Add the top-level projects ONLY to the host project.
|
||||
runtime project.childProjects.collect { n, p ->
|
||||
project(p.path)
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,72 @@ task jarFilter(type: JarFilterTask) {
|
||||
You can specify as many annotations for each role as you like. The only constraint is that a given
|
||||
annotation cannot be assigned to more than one role.
|
||||
|
||||
#### Removing unwanted default parameter values
|
||||
It is possible to assign non-deterministic expressions as default values for Kotlin constructors and functions. For
|
||||
example:
|
||||
```kotlin
|
||||
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID())
|
||||
```
|
||||
|
||||
The Kotlin compiler will generate _two_ constructors in this case:
|
||||
```
|
||||
UniqueIdentifier(String?, UUID)
|
||||
UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker)
|
||||
```
|
||||
|
||||
The first constructor is the primary constructor that we would expect (and which we'd like to keep), whereas the
|
||||
second is a public synthetic constructor that Kotlin applications invoke to handle the different combinations of
|
||||
default parameter values. Unfortunately, this synthetic constructor is therefore also part of the Kotlin ABI and
|
||||
so we _cannot_ rewrite the class like this to remove the default values:
|
||||
```kotlin
|
||||
// THIS REFACTOR WOULD BREAK THE KOTLIN ABI!
|
||||
data class UniqueIdentifier(val externalId: String?, val id: UUID) {
|
||||
constructor(externalId: String?) : this(externalId, UUID.randomUUID())
|
||||
constructor() : this(null)
|
||||
}
|
||||
```
|
||||
|
||||
The refactored class would have the following constructors, and would require client applications to be recompiled:
|
||||
```
|
||||
UniqueIdentifier(String?, UUID)
|
||||
UniqueIdentifier(String?)
|
||||
UniqueIdentifier()
|
||||
```
|
||||
|
||||
We therefore need to keep the default constructor parameters in order to preserve the ABI for the unfiltered code,
|
||||
which in turn means that `JarFilter` will need to delete only the synthetic constructor and leave the primary
|
||||
constructor intact. However, Kotlin does not currently allow us to annotate _specific_ constructors - see
|
||||
[KT-22524](https://youtrack.jetbrains.com/issue/KT-22524). Until it does, `JarFilter` will perform an initial
|
||||
"sanitising" pass over the JAR file to remove any unwanted annotations from the primary constructors. These unwanted
|
||||
annotations are configured in the `JarFilter` task definition:
|
||||
```gradle
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
...
|
||||
annotations {
|
||||
...
|
||||
forSanitise = [
|
||||
"org.testing.DeleteMe"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows us to annotate the `UniqueIdentifier` class like this:
|
||||
```kotlin
|
||||
data class UniqueIdentifier @DeleteMe constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID())
|
||||
```
|
||||
|
||||
to generate these constructors:
|
||||
```
|
||||
UniqueIdentifier(String?, UUID)
|
||||
@DeleteMe UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker)
|
||||
```
|
||||
|
||||
We currently **do not** sanitise annotations from functions with default parameter values, although (in theory) these
|
||||
may also be non-deterministic. We will need to extend the sanitation pass to include such functions if/when the need
|
||||
arises. At the moment, deleting such functions _entirely_ is enough, whereas also deleting a primary constructor means
|
||||
that we can no longer create instances of that class either.
|
||||
|
||||
### The `MetaFixer` task
|
||||
The `MetaFixer` task updates the `@kotlin.Metadata` annotations by removing references to any functions,
|
||||
constructors, properties or nested classes that no longer exist in the byte-code. This is primarily to
|
||||
|
@ -26,7 +26,7 @@ class FilterTransformer private constructor (
|
||||
private val unwantedFields: MutableSet<FieldElement>,
|
||||
private val deletedMethods: MutableSet<MethodElement>,
|
||||
private val stubbedMethods: MutableSet<MethodElement>
|
||||
) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable<FilterTransformer> {
|
||||
) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable<FilterTransformer> {
|
||||
constructor(
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
@ -47,8 +47,8 @@ class FilterTransformer private constructor (
|
||||
stubbedMethods = mutableSetOf()
|
||||
)
|
||||
|
||||
private var _className: String = "(unknown)"
|
||||
val className: String get() = _className
|
||||
var className: String = "(unknown)"
|
||||
private set
|
||||
|
||||
val isUnwantedClass: Boolean get() = isUnwantedClass(className)
|
||||
override val hasUnwantedElements: Boolean
|
||||
@ -76,7 +76,7 @@ class FilterTransformer private constructor (
|
||||
)
|
||||
|
||||
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
|
||||
_className = clsName
|
||||
className = clsName
|
||||
logger.info("Class {}", clsName)
|
||||
super.visit(version, access, clsName, signature, superName, interfaces)
|
||||
}
|
||||
@ -172,7 +172,7 @@ class FilterTransformer private constructor (
|
||||
/**
|
||||
* Removes the deleted methods and fields from the Kotlin Class metadata.
|
||||
*/
|
||||
override fun transformClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
val partitioned = deletedMethods.groupBy(MethodElement::isConstructor)
|
||||
val prefix = "$className$"
|
||||
return ClassMetadataTransformer(
|
||||
@ -191,7 +191,7 @@ class FilterTransformer private constructor (
|
||||
/**
|
||||
* Removes the deleted methods and fields from the Kotlin Package metadata.
|
||||
*/
|
||||
override fun transformPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return PackageMetadataTransformer(
|
||||
logger = logger,
|
||||
deletedFields = unwantedFields,
|
||||
|
@ -46,6 +46,9 @@ open class JarFilterTask : DefaultTask() {
|
||||
@get:Input
|
||||
protected var forRemove: Set<String> = emptySet()
|
||||
|
||||
@get:Input
|
||||
protected var forSanitise: Set<String> = emptySet()
|
||||
|
||||
fun annotations(assign: Closure<List<String>>) {
|
||||
assign.call()
|
||||
}
|
||||
@ -90,6 +93,9 @@ open class JarFilterTask : DefaultTask() {
|
||||
if (forRemove.isNotEmpty()) {
|
||||
logger.info("- Annotations '{}' will be removed entirely", forRemove.joinToString())
|
||||
}
|
||||
if (forSanitise.isNotEmpty()) {
|
||||
logger.info("- Annotations '{}' will be removed from primary constructors", forSanitise.joinToString())
|
||||
}
|
||||
checkDistinctAnnotations()
|
||||
try {
|
||||
jars.forEach { jar ->
|
||||
@ -136,6 +142,11 @@ open class JarFilterTask : DefaultTask() {
|
||||
private val source: Path = inFile.toPath()
|
||||
private val target: Path = toFiltered(inFile).toPath()
|
||||
|
||||
private val descriptorsForRemove = toDescriptors(forRemove)
|
||||
private val descriptorsForDelete = toDescriptors(forDelete)
|
||||
private val descriptorsForStub = toDescriptors(forStub)
|
||||
private val descriptorsForSanitising = toDescriptors(forSanitise)
|
||||
|
||||
init {
|
||||
Files.deleteIfExists(target)
|
||||
}
|
||||
@ -145,10 +156,14 @@ open class JarFilterTask : DefaultTask() {
|
||||
var input = source
|
||||
|
||||
try {
|
||||
if (descriptorsForSanitising.isNotEmpty() && SanitisingPass(input).use { it.run() }) {
|
||||
input = target.moveToInput()
|
||||
}
|
||||
|
||||
var passes = 1
|
||||
while (true) {
|
||||
verbose("Pass {}", passes)
|
||||
val isModified = Pass(input).use { it.run() }
|
||||
val isModified = FilterPass(input).use { it.run() }
|
||||
|
||||
if (!isModified) {
|
||||
logger.info("No changes after latest pass - exiting.")
|
||||
@ -157,9 +172,7 @@ open class JarFilterTask : DefaultTask() {
|
||||
break
|
||||
}
|
||||
|
||||
input = Files.move(
|
||||
target, Files.createTempFile(target.parent, "filter-", ".tmp"), REPLACE_EXISTING)
|
||||
verbose("New input JAR: {}", input)
|
||||
input = target.moveToInput()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error filtering '{}' elements from {}", ArrayList(forRemove).apply { addAll(forDelete); addAll(forStub) }, input)
|
||||
@ -167,14 +180,20 @@ open class JarFilterTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Pass(input: Path): Closeable {
|
||||
private fun Path.moveToInput(): Path {
|
||||
return Files.move(this, Files.createTempFile(parent, "filter-", ".tmp"), REPLACE_EXISTING).also {
|
||||
verbose("New input JAR: {}", it)
|
||||
}
|
||||
}
|
||||
|
||||
private abstract inner class Pass(input: Path): Closeable {
|
||||
/**
|
||||
* Use [ZipFile] instead of [java.util.jar.JarInputStream] because
|
||||
* JarInputStream consumes MANIFEST.MF when it's the first or second entry.
|
||||
*/
|
||||
private val inJar = ZipFile(input.toFile())
|
||||
private val outJar = ZipOutputStream(Files.newOutputStream(target))
|
||||
private var isModified = false
|
||||
protected val inJar = ZipFile(input.toFile())
|
||||
protected val outJar = ZipOutputStream(Files.newOutputStream(target))
|
||||
protected var isModified = false
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
@ -183,6 +202,8 @@ open class JarFilterTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun transform(inBytes: ByteArray): ByteArray
|
||||
|
||||
fun run(): Boolean {
|
||||
outJar.setLevel(BEST_COMPRESSION)
|
||||
outJar.setComment(inJar.comment)
|
||||
@ -207,16 +228,29 @@ open class JarFilterTask : DefaultTask() {
|
||||
}
|
||||
return isModified
|
||||
}
|
||||
}
|
||||
|
||||
private fun transform(inBytes: ByteArray): ByteArray {
|
||||
private inner class SanitisingPass(input: Path) : Pass(input) {
|
||||
override fun transform(inBytes: ByteArray): ByteArray {
|
||||
return ClassWriter(0).let { writer ->
|
||||
val transformer = SanitisingTransformer(writer, logger, descriptorsForSanitising)
|
||||
ClassReader(inBytes).accept(transformer, 0)
|
||||
isModified = isModified or transformer.isModified
|
||||
writer.toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FilterPass(input: Path) : Pass(input) {
|
||||
override fun transform(inBytes: ByteArray): ByteArray {
|
||||
var reader = ClassReader(inBytes)
|
||||
var writer = ClassWriter(COMPUTE_MAXS)
|
||||
var transformer = FilterTransformer(
|
||||
visitor = writer,
|
||||
logger = logger,
|
||||
removeAnnotations = toDescriptors(forRemove),
|
||||
deleteAnnotations = toDescriptors(forDelete),
|
||||
stubAnnotations = toDescriptors(forStub),
|
||||
removeAnnotations = descriptorsForRemove,
|
||||
deleteAnnotations = descriptorsForDelete,
|
||||
stubAnnotations = descriptorsForStub,
|
||||
unwantedClasses = unwantedClasses
|
||||
)
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
|
||||
import org.objectweb.asm.AnnotationVisitor
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
|
||||
/**
|
||||
* Kotlin support: Loads the ProtoBuf data from the [kotlin.Metadata] annotation,
|
||||
* or writes new ProtoBuf data that was created during a previous pass.
|
||||
* Kotlin support: Loads the ProtoBuf data from the [kotlin.Metadata] annotation.
|
||||
*/
|
||||
abstract class KotlinAwareVisitor(
|
||||
api: Int,
|
||||
@ -27,23 +27,24 @@ abstract class KotlinAwareVisitor(
|
||||
private var classKind: Int = 0
|
||||
|
||||
open val hasUnwantedElements: Boolean get() = kotlinMetadata.isNotEmpty()
|
||||
protected open val level: LogLevel = LogLevel.INFO
|
||||
|
||||
protected abstract fun transformClassMetadata(d1: List<String>, d2: List<String>): List<String>
|
||||
protected abstract fun transformPackageMetadata(d1: List<String>, d2: List<String>): List<String>
|
||||
protected abstract fun processClassMetadata(d1: List<String>, d2: List<String>): List<String>
|
||||
protected abstract fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String>
|
||||
protected abstract fun processKotlinAnnotation()
|
||||
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
val av = super.visitAnnotation(descriptor, visible) ?: return null
|
||||
return if (descriptor == METADATA_DESC) KotlinMetadataAdaptor(av) else av
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
protected fun processMetadata() {
|
||||
if (kotlinMetadata.isNotEmpty()) {
|
||||
logger.info("- Examining Kotlin @Metadata[k={}]", classKind)
|
||||
logger.log(level, "- Examining Kotlin @Metadata[k={}]", classKind)
|
||||
val d1 = kotlinMetadata.remove(METADATA_DATA_FIELD_NAME)
|
||||
val d2 = kotlinMetadata.remove(METADATA_STRINGS_FIELD_NAME)
|
||||
if (d1 != null && d1.isNotEmpty() && d2 != null) {
|
||||
transformMetadata(d1, d2).apply {
|
||||
processMetadata(d1, d2).apply {
|
||||
if (isNotEmpty()) {
|
||||
kotlinMetadata[METADATA_DATA_FIELD_NAME] = this
|
||||
kotlinMetadata[METADATA_STRINGS_FIELD_NAME] = d2
|
||||
@ -53,12 +54,12 @@ abstract class KotlinAwareVisitor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
private fun processMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return when (classKind) {
|
||||
KOTLIN_CLASS -> transformClassMetadata(d1, d2)
|
||||
KOTLIN_FILE, KOTLIN_MULTIFILE_PART -> transformPackageMetadata(d1, d2)
|
||||
KOTLIN_CLASS -> processClassMetadata(d1, d2)
|
||||
KOTLIN_FILE, KOTLIN_MULTIFILE_PART -> processPackageMetadata(d1, d2)
|
||||
KOTLIN_SYNTHETIC -> {
|
||||
logger.info("-- synthetic class ignored")
|
||||
logger.log(level,"-- synthetic class ignored")
|
||||
emptyList()
|
||||
}
|
||||
else -> {
|
||||
@ -66,7 +67,7 @@ abstract class KotlinAwareVisitor(
|
||||
* For class-kind=4 (i.e. "multi-file"), we currently
|
||||
* expect d1=[list of multi-file-part classes], d2=null.
|
||||
*/
|
||||
logger.info("-- unsupported class-kind {}", classKind)
|
||||
logger.log(level,"-- unsupported class-kind {}", classKind)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
@ -91,19 +92,68 @@ abstract class KotlinAwareVisitor(
|
||||
return null
|
||||
}
|
||||
|
||||
private inner class ArrayAccumulator(av: AnnotationVisitor, private val name: String) : AnnotationVisitor(api, av) {
|
||||
private val data: MutableList<String> = mutableListOf()
|
||||
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
super.visit(name, value)
|
||||
data.add(value as String)
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
kotlinMetadata[name] = data
|
||||
logger.debug("-- read @Metadata.{}[{}]", name, data.size)
|
||||
}
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
processKotlinAnnotation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ArrayAccumulator(av: AnnotationVisitor, private val name: String) : AnnotationVisitor(api, av) {
|
||||
private val data: MutableList<String> = mutableListOf()
|
||||
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
super.visit(name, value)
|
||||
data.add(value as String)
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
kotlinMetadata[name] = data
|
||||
logger.debug("-- read @Metadata.{}[{}]", name, data.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the ProtoBuf data from the [kotlin.Metadata] annotation, or
|
||||
* writes new ProtoBuf data that was created during a previous pass.
|
||||
*/
|
||||
abstract class KotlinAfterProcessor(
|
||||
api: Int,
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
kotlinMetadata: MutableMap<String, List<String>>
|
||||
) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) {
|
||||
|
||||
/**
|
||||
* Process the metadata once we have finished visiting the class.
|
||||
* This will allow us to rewrite the [kotlin.Metadata] annotation
|
||||
* in the next visit.
|
||||
*/
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
processMetadata()
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing immediately after we have parsed [kotlin.Metadata].
|
||||
*/
|
||||
final override fun processKotlinAnnotation() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the ProtoBuf data from the [kotlin.Metadata] annotation
|
||||
* and then processes it before visiting the rest of the class.
|
||||
*/
|
||||
abstract class KotlinBeforeProcessor(
|
||||
api: Int,
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
kotlinMetadata: MutableMap<String, List<String>>
|
||||
) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) {
|
||||
|
||||
/**
|
||||
* Process the ProtoBuf data as soon as we have parsed [kotlin.Metadata].
|
||||
*/
|
||||
final override fun processKotlinAnnotation() = processMetadata()
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class MetaFixerVisitor private constructor(
|
||||
private val fields: MutableSet<FieldElement>,
|
||||
private val methods: MutableSet<String>,
|
||||
private val nestedClasses: MutableSet<String>
|
||||
) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable<MetaFixerVisitor> {
|
||||
) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable<MetaFixerVisitor> {
|
||||
constructor(visitor: ClassVisitor, logger: Logger, classNames: Set<String>)
|
||||
: this(visitor, logger, mutableMapOf(), classNames, mutableSetOf(), mutableSetOf(), mutableSetOf())
|
||||
|
||||
@ -52,7 +52,7 @@ class MetaFixerVisitor private constructor(
|
||||
return super.visitInnerClass(clsName, outerName, innerName, access)
|
||||
}
|
||||
|
||||
override fun transformClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return ClassMetaFixerTransformer(
|
||||
logger = logger,
|
||||
actualFields = fields,
|
||||
@ -64,7 +64,7 @@ class MetaFixerVisitor private constructor(
|
||||
.transform()
|
||||
}
|
||||
|
||||
override fun transformPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return PackageMetaFixerTransformer(
|
||||
logger = logger,
|
||||
actualFields = fields,
|
||||
|
@ -0,0 +1,81 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.*
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.getJvmConstructorSignature
|
||||
import org.objectweb.asm.AnnotationVisitor
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
/**
|
||||
* This is (hopefully?!) a temporary solution for classes with [JvmOverloads] constructors.
|
||||
* We need to be able to annotate ONLY the secondary constructors for such classes, but Kotlin
|
||||
* will apply any annotation to all constructors equally. Nor can we replace the overloaded
|
||||
* constructor with individual constructors because this will break ABI compatibility. (Kotlin
|
||||
* generates a synthetic public constructor to handle default parameter values.)
|
||||
*
|
||||
* This transformer identifies a class's primary constructor and removes all of its unwanted annotations.
|
||||
* It will become superfluous when Kotlin allows us to target only the secondary constructors with our
|
||||
* filtering annotations in the first place.
|
||||
*/
|
||||
class SanitisingTransformer(visitor: ClassVisitor, logger: Logger, private val unwantedAnnotations: Set<String>)
|
||||
: KotlinBeforeProcessor(ASM6, visitor, logger, mutableMapOf()) {
|
||||
|
||||
var isModified: Boolean = false
|
||||
private set
|
||||
override val level: LogLevel = LogLevel.DEBUG
|
||||
|
||||
private var className: String = "(unknown)"
|
||||
private var primaryConstructor: MethodElement? = null
|
||||
|
||||
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> = emptyList()
|
||||
|
||||
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
|
||||
val stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
|
||||
val nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
|
||||
val message = ProtoBuf.Class.parseFrom(input, EXTENSION_REGISTRY)
|
||||
val typeTable = TypeTable(message.typeTable)
|
||||
|
||||
for (constructor in message.constructorList) {
|
||||
if (!IS_SECONDARY.get(constructor.flags)) {
|
||||
val signature = getJvmConstructorSignature(constructor, nameResolver, typeTable) ?: break
|
||||
primaryConstructor = MethodElement("<init>", signature.drop("<init>".length))
|
||||
logger.log(level, "Class {} has primary constructor {}", className, signature)
|
||||
break
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
|
||||
className = clsName
|
||||
super.visit(version, access, clsName, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
|
||||
val method = MethodElement(methodName, descriptor, access)
|
||||
val mv = super.visitMethod(access, methodName, descriptor, signature, exceptions) ?: return null
|
||||
return if (method == primaryConstructor) SanitisingMethodAdapter(mv, method) else mv
|
||||
}
|
||||
|
||||
private inner class SanitisingMethodAdapter(mv: MethodVisitor, private val method: MethodElement) : MethodVisitor(api, mv) {
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (unwantedAnnotations.contains(descriptor)) {
|
||||
logger.info("Sanitising annotation {} from method {}.{}{}", descriptor, className, method.name, method.descriptor)
|
||||
isModified = true
|
||||
return null
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible)
|
||||
}
|
||||
}
|
||||
}
|
@ -73,17 +73,20 @@ internal val String.descriptor: String get() = "L$toPathFormat;"
|
||||
internal fun <T> ByteArray.execute(visitor: (ClassVisitor) -> T, flags: Int = 0, passes: Int = 2): ByteArray
|
||||
where T : ClassVisitor,
|
||||
T : Repeatable<T> {
|
||||
var reader = ClassReader(this)
|
||||
var bytecode = this
|
||||
var writer = ClassWriter(flags)
|
||||
val transformer = visitor(writer)
|
||||
var transformer = visitor(writer)
|
||||
var count = max(passes, 1)
|
||||
|
||||
reader.accept(transformer, 0)
|
||||
while (transformer.hasUnwantedElements && --count > 0) {
|
||||
reader = ClassReader(writer.toByteArray())
|
||||
while (--count >= 0) {
|
||||
ClassReader(bytecode).accept(transformer, 0)
|
||||
bytecode = writer.toByteArray()
|
||||
|
||||
if (!transformer.hasUnwantedElements) break
|
||||
|
||||
writer = ClassWriter(flags)
|
||||
reader.accept(transformer.recreate(writer), 0)
|
||||
transformer = transformer.recreate(writer)
|
||||
}
|
||||
|
||||
return writer.toByteArray()
|
||||
return bytecode
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class DeleteConstructorTest {
|
||||
|
||||
@Test
|
||||
fun deleteConstructorWithLongParameter() {
|
||||
val longConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(Long::class))
|
||||
val longConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
@ -58,7 +58,7 @@ class DeleteConstructorTest {
|
||||
|
||||
@Test
|
||||
fun deleteConstructorWithStringParameter() {
|
||||
val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(String::class))
|
||||
val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
@ -80,7 +80,7 @@ class DeleteConstructorTest {
|
||||
|
||||
@Test
|
||||
fun showUnannotatedConstructorIsUnaffected() {
|
||||
val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(Int::class))
|
||||
val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Int::class)
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasAll>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
@ -96,7 +96,7 @@ class DeleteConstructorTest {
|
||||
|
||||
@Test
|
||||
fun deletePrimaryConstructorWithStringParameter() {
|
||||
val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, hasParam(String::class))
|
||||
val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
@ -119,7 +119,7 @@ class DeleteConstructorTest {
|
||||
|
||||
@Test
|
||||
fun deletePrimaryConstructorWithLongParameter() {
|
||||
val longConstructor = isConstructor(LONG_PRIMARY_CONSTRUCTOR_CLASS, hasParam(Long::class))
|
||||
val longConstructor = isConstructor(LONG_PRIMARY_CONSTRUCTOR_CLASS, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
@ -142,7 +142,7 @@ class DeleteConstructorTest {
|
||||
|
||||
@Test
|
||||
fun deletePrimaryConstructorWithIntParameter() {
|
||||
val intConstructor = isConstructor(INT_PRIMARY_CONSTRUCTOR_CLASS, hasParam(Int::class))
|
||||
val intConstructor = isConstructor(INT_PRIMARY_CONSTRUCTOR_CLASS, Int::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
|
@ -0,0 +1,183 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import net.corda.gradle.unwanted.HasLong
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class SanitiseConstructorTest {
|
||||
companion object {
|
||||
private const val COUNT_INITIAL_OVERLOADED = 1
|
||||
private const val COUNT_INITIAL_MULTIPLE = 2
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "sanitise-constructor")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteOverloadedLongConstructor() = checkClassWithLongParameter(
|
||||
"net.corda.gradle.HasOverloadedLongConstructor",
|
||||
COUNT_INITIAL_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun deleteMultipleLongConstructor() = checkClassWithLongParameter(
|
||||
"net.corda.gradle.HasMultipleLongConstructors",
|
||||
COUNT_INITIAL_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithLongParameter(longClass: String, initialCount: Int) {
|
||||
val longConstructor = isConstructor(longClass, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(longClass).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(J) not found", this, hasItem(longConstructor))
|
||||
assertEquals(initialCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
|
||||
|
||||
newInstance().also {
|
||||
assertEquals(0, it.longData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLong>(longClass).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(J) not found", this, hasItem(longConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
|
||||
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteOverloadedIntConstructor() = checkClassWithIntParameter(
|
||||
"net.corda.gradle.HasOverloadedIntConstructor",
|
||||
COUNT_INITIAL_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun deleteMultipleIntConstructor() = checkClassWithIntParameter(
|
||||
"net.corda.gradle.HasMultipleIntConstructors",
|
||||
COUNT_INITIAL_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithIntParameter(intClass: String, initialCount: Int) {
|
||||
val intConstructor = isConstructor(intClass, Int::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(intClass).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(I) not found", this, hasItem(intConstructor))
|
||||
assertEquals(initialCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
|
||||
|
||||
//assertThat("", constructors, hasItem(isConstructor(""))
|
||||
newInstance().also {
|
||||
assertEquals(0, it.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(intClass).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(I) not found", this, hasItem(intConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
|
||||
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteOverloadedStringConstructor() = checkClassWithStringParameter(
|
||||
"net.corda.gradle.HasOverloadedStringConstructor",
|
||||
COUNT_INITIAL_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun deleteMultipleStringConstructor() = checkClassWithStringParameter(
|
||||
"net.corda.gradle.HasMultipleStringConstructors",
|
||||
COUNT_INITIAL_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithStringParameter(stringClass: String, initialCount: Int) {
|
||||
val stringConstructor = isConstructor(stringClass, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(stringClass).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
|
||||
assertEquals(initialCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
|
||||
|
||||
newInstance().also {
|
||||
assertEquals(DEFAULT_MESSAGE, it.stringData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(stringClass).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
|
||||
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,10 +13,12 @@ fun isMethod(name: Matcher<in String>, returnType: Matcher<in Class<*>>, vararg
|
||||
}
|
||||
|
||||
fun isMethod(name: String, returnType: Class<*>, vararg parameters: Class<*>): Matcher<Method> {
|
||||
return isMethod(equalTo(name), equalTo(returnType), *parameters.map(::equalTo).toTypedArray())
|
||||
return isMethod(equalTo(name), equalTo(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
val <T: Any> KClass<T>.javaDeclaredMethods: List<Method> get() = java.declaredMethods.toList()
|
||||
private fun Array<out Class<*>>.toMatchers() = map(::equalTo).toTypedArray()
|
||||
|
||||
val KClass<*>.javaDeclaredMethods: List<Method> get() = java.declaredMethods.toList()
|
||||
|
||||
/**
|
||||
* Matcher logic for a Java [Method] object. Also applicable to constructors.
|
||||
|
@ -17,7 +17,7 @@ fun isFunction(name: Matcher<in String>, returnType: Matcher<in String>, vararg
|
||||
}
|
||||
|
||||
fun isFunction(name: String, returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
|
||||
return isFunction(equalTo(name), matches(returnType), *parameters.map(::hasParam).toTypedArray())
|
||||
return isFunction(equalTo(name), matches(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
fun isConstructor(returnType: Matcher<in String>, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
|
||||
@ -25,11 +25,11 @@ fun isConstructor(returnType: Matcher<in String>, vararg parameters: Matcher<in
|
||||
}
|
||||
|
||||
fun isConstructor(returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
|
||||
return isConstructor(matches(returnType), *parameters.map(::hasParam).toTypedArray())
|
||||
return isConstructor(matches(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
fun isConstructor(returnType: String, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
|
||||
return isConstructor(equalTo(returnType), *parameters)
|
||||
fun isConstructor(returnType: String, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
|
||||
return isConstructor(equalTo(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
fun hasParam(type: Matcher<in String>): Matcher<KParameter> = KParameterMatcher(type)
|
||||
@ -44,6 +44,8 @@ fun isClass(name: String): Matcher<KClass<*>> = KClassMatcher(equalTo(name))
|
||||
|
||||
fun matches(type: KClass<*>): Matcher<in String> = equalTo(type.qualifiedName)
|
||||
|
||||
private fun Array<out KClass<*>>.toMatchers() = map(::hasParam).toTypedArray()
|
||||
|
||||
/**
|
||||
* Matcher logic for a Kotlin [KFunction] object. Also applicable to constructors.
|
||||
*/
|
||||
|
@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/sanitise-constructor/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'sanitise-constructor'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
forSanitise = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.unwanted.*
|
||||
|
||||
private const val DEFAULT_MESSAGE = "<default-value>"
|
||||
|
||||
class HasOverloadedStringConstructor @JvmOverloads @DeleteMe constructor(private val message: String = DEFAULT_MESSAGE) : HasString {
|
||||
override fun stringData(): String = message
|
||||
}
|
||||
|
||||
class HasOverloadedLongConstructor @JvmOverloads @DeleteMe constructor(private val data: Long = 0) : HasLong {
|
||||
override fun longData(): Long = data
|
||||
}
|
||||
|
||||
class HasOverloadedIntConstructor @JvmOverloads @DeleteMe constructor(private val data: Int = 0) : HasInt {
|
||||
override fun intData(): Int = data
|
||||
}
|
||||
|
||||
class HasMultipleStringConstructors(private val message: String) : HasString {
|
||||
@DeleteMe constructor() : this(DEFAULT_MESSAGE)
|
||||
override fun stringData(): String = message
|
||||
}
|
||||
|
||||
class HasMultipleLongConstructors(private val data: Long) : HasLong {
|
||||
@DeleteMe constructor() : this(0)
|
||||
override fun longData(): Long = data
|
||||
}
|
||||
|
||||
class HasMultipleIntConstructors(private val data: Int) : HasInt {
|
||||
@DeleteMe constructor() : this(0)
|
||||
override fun intData(): Int = data
|
||||
}
|
@ -107,7 +107,7 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
|
||||
protected val methodMap: Multimap<String, Method> = methodsFromType(targetType)
|
||||
|
||||
/** A map of method name to parameter names for the target type. */
|
||||
val methodParamNames: Map<String, List<String>> = targetType.declaredMethods.mapNotNull {
|
||||
val methodParamNames: Map<String, List<String>> = targetType.declaredMethods.filterNot(Method::isSynthetic).mapNotNull {
|
||||
try {
|
||||
it.name to paramNamesFromMethod(it)
|
||||
} catch (e: KotlinReflectionInternalError) {
|
||||
|
@ -27,7 +27,7 @@ import net.corda.finance.flows.CashPaymentFlow.PaymentRequest
|
||||
|
||||
open class EventGenerator(val parties: List<Party>, val currencies: List<Currency>, val notary: Party) {
|
||||
protected val partyGenerator = Generator.pickOne(parties)
|
||||
protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
|
||||
protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes.of(number.toByte()) }
|
||||
protected val amountGenerator = Generator.longRange(10000, 1000000)
|
||||
protected val currencyGenerator = Generator.pickOne(currencies)
|
||||
protected val currencyMap: MutableMap<Currency, Long> = mutableMapOf(USD to 0L, GBP to 0L) // Used for estimation of how much money we have in general.
|
||||
|
@ -54,7 +54,7 @@ class FlowsExecutionModeRpcTest : IntegrationTest() {
|
||||
assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
|
||||
|
||||
val user = User("mark", "dadada", setOf(invokeRpc("setFlowsDrainingModeEnabled"), invokeRpc("isFlowsDrainingModeEnabled")))
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = true)) {
|
||||
driver(DriverParameters(isDebug = true, inMemoryDB = false, startNodesInProcess = true)) {
|
||||
val nodeName = {
|
||||
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||
val nodeName = nodeHandle.nodeInfo.chooseIdentity().name
|
||||
|
@ -9,7 +9,7 @@
|
||||
#
|
||||
|
||||
gradlePluginsVersion=4.0.25
|
||||
kotlinVersion=1.2.50
|
||||
kotlinVersion=1.2.51
|
||||
platformVersion=4
|
||||
guavaVersion=21.0
|
||||
proguardVersion=6.0.3
|
||||
|
@ -86,7 +86,7 @@ task predeterminise(type: ProGuardTask) {
|
||||
|
||||
keepattributes '*'
|
||||
keepdirectories
|
||||
dontwarn '**$1$1'
|
||||
dontwarn '**$1$1,org.hibernate.annotations.*'
|
||||
dontpreverify
|
||||
dontobfuscate
|
||||
dontoptimize
|
||||
@ -112,7 +112,11 @@ task jarFilter(type: JarFilterTask) {
|
||||
"net.corda.core.StubOutForDJVM"
|
||||
]
|
||||
forRemove = [
|
||||
"co.paralleluniverse.fibers.Suspendable"
|
||||
"co.paralleluniverse.fibers.Suspendable",
|
||||
"org.hibernate.annotations.Immutable"
|
||||
]
|
||||
forSanitise = [
|
||||
"net.corda.core.DeleteForDJVM"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -23,3 +23,6 @@ dependencies {
|
||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||
testCompile "junit:junit:$junit_version"
|
||||
}
|
||||
|
||||
// This module has no artifact and only contains tests.
|
||||
jar.enabled = false
|
||||
|
@ -5,7 +5,6 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import java.io.OutputStream
|
||||
import java.math.BigInteger
|
||||
import java.math.BigInteger.TEN
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
|
@ -11,7 +11,7 @@ class PrivacySaltTest {
|
||||
|
||||
@Test
|
||||
fun testValidSalt() {
|
||||
PrivacySalt(ByteArray(SALT_SIZE, { 0x14 }))
|
||||
PrivacySalt(ByteArray(SALT_SIZE) { 0x14 })
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -22,13 +22,13 @@ class PrivacySaltTest {
|
||||
|
||||
@Test
|
||||
fun testTooShortPrivacySalt() {
|
||||
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE - 1, { 0x7f })) }
|
||||
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE - 1) { 0x7f }) }
|
||||
assertEquals("Privacy salt should be 32 bytes.", ex.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTooLongPrivacySalt() {
|
||||
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE + 1, { 0x7f })) }
|
||||
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE + 1) { 0x7f }) }
|
||||
assertEquals("Privacy salt should be 32 bytes.", ex.message)
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.corda.deterministic.contracts
|
||||
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class UniqueIdentifierTest {
|
||||
private companion object {
|
||||
private const val NAME = "MyName"
|
||||
private val TEST_UUID: UUID = UUID.fromString("00000000-1111-2222-3333-444444444444")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNewInstance() {
|
||||
val id = UniqueIdentifier(NAME, TEST_UUID)
|
||||
assertEquals("${NAME}_$TEST_UUID", id.toString())
|
||||
assertEquals(NAME, id.externalId)
|
||||
assertEquals(TEST_UUID, id.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrimaryConstructor() {
|
||||
val primary = UniqueIdentifier::class.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(NAME, TEST_UUID)).isEqualTo(UniqueIdentifier(NAME, TEST_UUID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConstructors() {
|
||||
assertEquals(1, UniqueIdentifier::class.constructors.size)
|
||||
val ex = assertFailsWith<IllegalArgumentException> { UniqueIdentifier::class.constructors.first().call() }
|
||||
assertThat(ex).hasMessage("Callable expects 2 arguments, but 0 were provided.")
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ class SecureHashTest {
|
||||
@Test
|
||||
fun testConstants() {
|
||||
assertArrayEquals(SecureHash.zeroHash.bytes, ByteArray(32))
|
||||
assertArrayEquals(SecureHash.allOnesHash.bytes, ByteArray(32, { 0xFF.toByte() }))
|
||||
assertArrayEquals(SecureHash.allOnesHash.bytes, ByteArray(32) { 0xFF.toByte() })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,10 +30,7 @@ import java.util.*
|
||||
*/
|
||||
@CordaSerializable
|
||||
@KeepForDJVM
|
||||
data class UniqueIdentifier(val externalId: String?, val id: UUID) : Comparable<UniqueIdentifier> {
|
||||
@DeleteForDJVM constructor(externalId: String?) : this(externalId, UUID.randomUUID())
|
||||
@DeleteForDJVM constructor() : this(null)
|
||||
|
||||
data class UniqueIdentifier @JvmOverloads @DeleteForDJVM constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable<UniqueIdentifier> {
|
||||
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()
|
||||
|
||||
companion object {
|
||||
|
@ -104,7 +104,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
* This field provides more intuitive access from Java.
|
||||
*/
|
||||
@JvmField
|
||||
val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 0.toByte() }))
|
||||
val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 0.toByte() })
|
||||
|
||||
/**
|
||||
* A SHA-256 hash value consisting of 32 0x00 bytes.
|
||||
@ -118,7 +118,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
* This field provides more intuitive access from Java.
|
||||
*/
|
||||
@JvmField
|
||||
val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 255.toByte() }))
|
||||
val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 255.toByte() })
|
||||
|
||||
/**
|
||||
* A SHA-256 hash value consisting of 32 0xFF bytes.
|
||||
|
@ -15,6 +15,7 @@ import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.toHexString
|
||||
import org.hibernate.annotations.Immutable
|
||||
import java.io.Serializable
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Embeddable
|
||||
@ -100,6 +101,7 @@ class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : St
|
||||
*/
|
||||
@KeepForDJVM
|
||||
@Embeddable
|
||||
@Immutable
|
||||
data class PersistentStateRef(
|
||||
@Column(name = "transaction_id", length = 64, nullable = false)
|
||||
var txId: String,
|
||||
@ -114,7 +116,8 @@ data class PersistentStateRef(
|
||||
* Marker interface to denote a persistable Corda state entity that will always have a transaction id and index
|
||||
*/
|
||||
@KeepForDJVM
|
||||
interface StatePersistable : Serializable
|
||||
interface StatePersistable
|
||||
|
||||
object MappedSchemaValidator {
|
||||
fun fieldsFromOtherMappedSchema(schema: MappedSchema) : List<SchemaCrossReferenceReport> =
|
||||
schema.mappedTypes.map { entity ->
|
||||
|
@ -12,6 +12,9 @@ Unreleased
|
||||
and within that file the ``bridgeMode`` propety has been modified to ``firewallMode`` for overall consistency.
|
||||
This will be a breaking change for early adopters and their deployments, but hopefully will be more future proof.
|
||||
|
||||
* The Corda JPA entities no longer implement java.io.Serializable, as this was causing persistence errors in obscure cases.
|
||||
Java serialization is disabled globally in the node, but in the unlikely event you were relying on these types being Java serializable please contact us.
|
||||
|
||||
* Remove all references to the out-of-process transaction verification.
|
||||
|
||||
* 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.
|
||||
|
@ -12,6 +12,7 @@ Nodes
|
||||
clientrpc
|
||||
shell
|
||||
node-database
|
||||
node-database-access-h2
|
||||
node-administration
|
||||
node-operations-upgrading
|
||||
node-operations-upgrade-cordapps
|
||||
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
@ -10,34 +10,34 @@ Kubernetes for parts specific to that.
|
||||
|
||||
The main idea behind the infrastructure is to provide a highly available cluster of enclave services (hosts) which can
|
||||
serve enclaves on demand. It provides an interface for enclave business logic that's agnostic with regards to the
|
||||
infrastructure, similar to [serverless architectures](details/serverless.md). The enclaves will use an opaque reference
|
||||
to other enclaves or services in the form of [enclave channels](details/channels.md). Channels hides attestation details
|
||||
infrastructure, similar to serverless architectures. The enclaves will use an opaque reference
|
||||
to other enclaves or services in the form of enclave channels. Channels hides attestation details
|
||||
and provide a loose coupling between enclave/non-enclave functionality and specific enclave images/services implementing
|
||||
it. This loose coupling allows easier upgrade of enclaves, relaxed trust (whitelisting), dynamic deployment, and
|
||||
horizontal scaling as we can spin up enclaves dynamically on demand when a channel is requested.
|
||||
|
||||
For more information see:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
details/serverless.md
|
||||
details/channels.md
|
||||
|
||||
## Infrastructure components
|
||||
|
||||
Here are the major components of the infrastructure. Note that this doesn't include business logic specific
|
||||
infrastructure pieces (like ORAM blob storage for Corda privacy model integration).
|
||||
|
||||
* [**Distributed key-value store**](details/kv-store.md):
|
||||
Responsible for maintaining metadata about enclaves, hosts, sealed secrets and CPU locality.
|
||||
|
||||
* [**Discovery service**](details/discovery.md)
|
||||
Responsible for resolving an enclave channel to a specific enclave image and a host that can serve it using the
|
||||
metadata in the key-value store.
|
||||
|
||||
* [**Enclave host**](details/host.md):
|
||||
This is a service capable of serving enclaves and driving the underlying traffic. Third party components like Intel's
|
||||
SGX driver and aesmd also belong here.
|
||||
|
||||
* [**Enclave storage**](details/enclave-storage.md):
|
||||
Responsible for serving enclave images to hosts. This is a simple static content server.
|
||||
|
||||
* [**IAS proxy**](details/ias-proxy.md):
|
||||
This is an unfortunate necessity because of Intel's requirement to do mutual TLS with their services.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
details/kv-store.md
|
||||
details/discovery.md
|
||||
details/host.md
|
||||
details/enclave-storage.md
|
||||
details/ias-proxy.md
|
||||
|
||||
## Infrastructure interactions
|
||||
|
||||
* **Enclave deployment**:
|
||||
@ -54,17 +54,23 @@ infrastructure pieces (like ORAM blob storage for Corda privacy model integratio
|
||||
|
||||
## Decisions to be made
|
||||
|
||||
* [**Strategic roadmap**](decisions/roadmap.md)
|
||||
* [**CPU certification method**](decisions/certification.md)
|
||||
* [**Enclave language of choice**](decisions/enclave-language.md)
|
||||
* [**Key-value store**](decisions/kv-store.md)
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
decisions/roadmap.md
|
||||
decisions/certification.md
|
||||
decisions/enclave-language.md
|
||||
decisions/kv-store.md
|
||||
|
||||
## Further details
|
||||
|
||||
* [**Attestation**](details/attestation.md)
|
||||
* [**Calendar time for data at rest**](details/time.md)
|
||||
* [**Enclave deployment**](details/enclave-deployment.md)
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
details/attestation.md
|
||||
details/time.md
|
||||
details/enclave-deployment.md
|
||||
|
||||
## Example deployment
|
||||
|
||||
This is an example of how two Corda parties may use the above infrastructure. In this example R3 is hosting the IAS
|
||||
@ -75,4 +81,4 @@ the enclave image store (although R3 will need to have a repository of the signe
|
||||
We may also decide to go the other way and have R3 host the enclave hosts and the discovery service, shared between
|
||||
parties (if e.g. they don't have access to/want to maintain SGX capable boxes).
|
||||
|
||||
![Example SGX deployment](Example%20SGX%20deployment.png)
|
||||
![Example SGX deployment](ExampleSGXdeployment.png)
|
@ -62,15 +62,16 @@ The build generates each of Corda's deterministic JARs in six steps:
|
||||
|
||||
@CordaSerializable
|
||||
@KeepForDJVM
|
||||
data class UniqueIdentifier(val externalId: String?, val id: UUID) : Comparable<UniqueIdentifier> {
|
||||
@DeleteForDJVM constructor(externalId: String?) : this(externalId, UUID.randomUUID())
|
||||
@DeleteForDJVM constructor() : this(null)
|
||||
data class UniqueIdentifier @JvmOverloads @DeleteForDJVM constructor(
|
||||
val externalId: String? = null,
|
||||
val id: UUID = UUID.randomUUID()
|
||||
) : Comparable<UniqueIdentifier> {
|
||||
...
|
||||
}
|
||||
|
||||
..
|
||||
|
||||
While CorDapps will definitely need to handle ``UniqueIdentifier`` objects, both of the secondary constructors
|
||||
While CorDapps will definitely need to handle ``UniqueIdentifier`` objects, all of the secondary constructors
|
||||
generate a new random ``UUID`` and so are non-deterministic. Hence the next "determinising" step is to pass the
|
||||
classes to the ``JarFilter`` tool, which strips out all of the elements which have been annotated as
|
||||
``@DeleteForDJVM`` and stubs out any functions annotated with ``@StubOutForDJVM``. (Stub functions that
|
||||
@ -270,11 +271,34 @@ Non-Deterministic Elements
|
||||
..
|
||||
|
||||
You must also ensure that a deterministic class's primary constructor does not reference any classes that are
|
||||
not available in the deterministic ``rt.jar``, nor have any non-deterministic default parameter values such as
|
||||
``UUID.randomUUID()``. The biggest risk here would be that ``JarFilter`` would delete the primary constructor
|
||||
and that the class could no longer be instantiated, although ``JarFilter`` will print a warning in this case.
|
||||
However, it is also likely that the "determinised" class would have a different serialisation signature than
|
||||
its non-deterministic version and so become unserialisable on the deterministic JVM.
|
||||
not available in the deterministic ``rt.jar``. The biggest risk here would be that ``JarFilter`` would delete the
|
||||
primary constructor and that the class could no longer be instantiated, although ``JarFilter`` will print a warning
|
||||
in this case. However, it is also likely that the "determinised" class would have a different serialisation
|
||||
signature than its non-deterministic version and so become unserialisable on the deterministic JVM.
|
||||
|
||||
Primary constructors that have non-deterministic default parameter values must still be annotated as
|
||||
``@DeleteForDJVM`` because they cannot be refactored without breaking Corda's binary interface. The Kotlin compiler
|
||||
will automatically apply this ``@DeleteForDJVM`` annotation - along with any others - to all of the class's
|
||||
secondary constructors too. The ``JarFilter`` plugin can then remove the ``@DeleteForDJVM`` annotation from the
|
||||
primary constructor so that it can subsequently delete only the secondary constructors.
|
||||
|
||||
The annotations that ``JarFilter`` will "sanitise" from primary constructors in this way are listed in the plugin's
|
||||
configuration block, e.g.
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
...
|
||||
annotations {
|
||||
...
|
||||
|
||||
forSanitise = [
|
||||
"net.corda.core.DeleteForDJVM"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
..
|
||||
|
||||
Be aware that package-scoped Kotlin properties are all initialised within a common ``<clinit>`` block inside
|
||||
their host ``.class`` file. This means that when ``JarFilter`` deletes these properties, it cannot also remove
|
||||
|
@ -60,6 +60,7 @@ application development please continue to refer to `the main project documentat
|
||||
azure-vm.rst
|
||||
aws-vm.rst
|
||||
certificate-revocation
|
||||
loadtesting.rst
|
||||
|
||||
.. Documentation is not included in the pdf unless it is included in a toctree somewhere
|
||||
.. only:: pdfmode
|
||||
@ -68,4 +69,4 @@ application development please continue to refer to `the main project documentat
|
||||
:caption: Other documentation
|
||||
|
||||
deterministic-modules.rst
|
||||
changelog.rst
|
||||
changelog.rst
|
@ -82,8 +82,10 @@ This command line will start the node with JMX metrics accessible via HTTP on po
|
||||
|
||||
See :ref:`Monitoring your node <jolokia_ref>` for further details.
|
||||
|
||||
Starting all nodes at once from the command line
|
||||
------------------------------------------------
|
||||
Starting all nodes at once on a local machine from the command line
|
||||
-------------------------------------------------------------------
|
||||
|
||||
.. _starting-all-nodes-at-once:
|
||||
|
||||
Native
|
||||
~~~~~~
|
||||
@ -115,7 +117,35 @@ After the nodes are started up, you can use ``docker ps`` command to see how the
|
||||
and `Docker Compose documentation <https://docs.docker.com/compose/install/>`_ for installation instructions for all
|
||||
major operating systems.
|
||||
|
||||
Starting all nodes at once on a remote machine from the command line
|
||||
--------------------------------------------------------------------
|
||||
|
||||
By default, ``Cordform`` expects the nodes it generates to be run on the same machine where they were generated.
|
||||
In order to run the nodes remotely, the nodes can be deployed locally and then copied to a remote server.
|
||||
If after copying the nodes to the remote machine you encounter errors related to ``localhost`` resolution, you will additionally need to follow the steps below.
|
||||
|
||||
To create nodes locally and run on a remote machine perform the following steps:
|
||||
|
||||
1. Configure Cordform task and deploy the nodes locally as described in :doc:`generating-a-node`.
|
||||
|
||||
2. Copy the generated directory structure to a remote machine using e.g. Secure Copy.
|
||||
|
||||
3. Optionally, bootstrap the network on the remote machine.
|
||||
|
||||
This is optional step when a remote machine doesn't accept ``localhost`` addresses, or the generated nodes are configured to run on another host's IP address.
|
||||
|
||||
If required change host addresses in top level configuration files ``[NODE NAME]_node.conf`` for entries ``p2pAddress`` , ``rpcSettings.address`` and ``rpcSettings.adminAddress``.
|
||||
|
||||
Run the network bootstrapper tool to regenerate the nodes network map (see for more explanation :doc:`network-bootstrapper`):
|
||||
|
||||
``java -jar corda-tools-network-bootstrapper-Master.jar --dir <nodes-root-dir>``
|
||||
|
||||
4. Run nodes on the remote machine using :ref:`runnodes command <starting-all-nodes-at-once>`.
|
||||
|
||||
The above steps create a test deployment as ``deployNodes`` Gradle task would do on a local machine.
|
||||
|
||||
Database migrations
|
||||
-------------------
|
||||
Depending on the versions of Corda and of the CorDapps used, database migration scripts might need to run before a node is able to start.
|
||||
For more information refer to :doc:`database-management`.
|
||||
For more information refer to :doc:`database-management`.
|
||||
|
||||
|
@ -2,7 +2,6 @@ Serialization
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
|
||||
:caption: Other docs
|
||||
:maxdepth: 1
|
||||
|
||||
|
@ -33,8 +33,10 @@ with the node using RPC calls.
|
||||
The shell via the local terminal
|
||||
--------------------------------
|
||||
|
||||
In development mode, the shell will display in the node's terminal window.
|
||||
The shell connects to the node as 'shell' user with password 'shell' which is only available in dev mode.
|
||||
.. note:: Local terminal shell works only in development mode!
|
||||
|
||||
The shell will display in the node's terminal window. It connects to the node as 'shell' user with password 'shell'
|
||||
(which is only available in dev mode).
|
||||
It may be disabled by passing the ``--no-local-shell`` flag when running the node.
|
||||
|
||||
.. _ssh_server:
|
||||
|
@ -57,7 +57,7 @@ class NodeStatePersistenceTests : IntegrationTest() {
|
||||
fun `persistent state survives node restart`() {
|
||||
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
|
||||
val message = Message("Hello world!")
|
||||
val stateAndRef: StateAndRef<MessageState>? = driver(DriverParameters(isDebug = true, startNodesInProcess = isQuasarAgentSpecified(),
|
||||
val stateAndRef: StateAndRef<MessageState>? = driver(DriverParameters(isDebug = true, inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(),
|
||||
portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
|
||||
val nodeName = {
|
||||
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||
@ -90,7 +90,7 @@ class NodeStatePersistenceTests : IntegrationTest() {
|
||||
|
||||
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
|
||||
val message = Message("Hello world!")
|
||||
val stateAndRef: StateAndRef<MessageState>? = driver(DriverParameters(isDebug = true, startNodesInProcess = isQuasarAgentSpecified(), portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
|
||||
val stateAndRef: StateAndRef<MessageState>? = driver(DriverParameters(isDebug = true, inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(), portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
|
||||
val nodeName = {
|
||||
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||
val nodeName = nodeHandle.nodeInfo.singleIdentity().name
|
||||
|
@ -70,7 +70,7 @@ class HardRestartTest : IntegrationTest() {
|
||||
@Test
|
||||
fun restartShortPingPongFlowRandomly() {
|
||||
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<Ping>(), Permissions.all()))
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
val (a, b) = listOf(
|
||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||
@ -102,7 +102,7 @@ class HardRestartTest : IntegrationTest() {
|
||||
@Test
|
||||
fun restartLongPingPongFlowRandomly() {
|
||||
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<Ping>(), Permissions.all()))
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
val (a, b) = listOf(
|
||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||
@ -134,7 +134,7 @@ class HardRestartTest : IntegrationTest() {
|
||||
@Test
|
||||
fun softRestartLongPingPongFlowRandomly() {
|
||||
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<Ping>(), Permissions.all()))
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
val (a, b) = listOf(
|
||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||
@ -210,7 +210,7 @@ class HardRestartTest : IntegrationTest() {
|
||||
@Test
|
||||
fun restartRecursiveFlowRandomly() {
|
||||
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<RecursiveA>(), Permissions.all()))
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||
val (a, b) = listOf(
|
||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||
|
@ -20,7 +20,6 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.persistence.NodePropertiesPersistentStore
|
||||
import java.io.Serializable
|
||||
import javax.persistence.*
|
||||
|
||||
object NodeInfoSchema
|
||||
@ -66,7 +65,7 @@ object NodeInfoSchemaV1 : MappedSchema(
|
||||
*/
|
||||
@Column(name = "serial", nullable = false)
|
||||
val serial: Long
|
||||
) : Serializable {
|
||||
) {
|
||||
fun toNodeInfo(): NodeInfo {
|
||||
return NodeInfo(
|
||||
this.addresses.map { it.toHostAndPort() },
|
||||
@ -86,7 +85,7 @@ object NodeInfoSchemaV1 : MappedSchema(
|
||||
var id: Int,
|
||||
val host: String? = null,
|
||||
val port: Int? = null
|
||||
) : Serializable {
|
||||
) {
|
||||
companion object {
|
||||
fun fromHostAndPort(hostAndPort: NetworkHostAndPort) = DBHostAndPort(
|
||||
0, hostAndPort.host, hostAndPort.port
|
||||
@ -119,7 +118,7 @@ object NodeInfoSchemaV1 : MappedSchema(
|
||||
|
||||
@ManyToMany(mappedBy = "legalIdentitiesAndCerts", cascade = [(CascadeType.ALL)]) // ManyToMany because of distributed services.
|
||||
private val persistentNodeInfos: Set<PersistentNodeInfo> = emptySet()
|
||||
) : Serializable {
|
||||
) {
|
||||
constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false)
|
||||
: this(partyAndCert.name.toString(),
|
||||
partyAndCert.party.owningKey.toStringShort(),
|
||||
|
@ -21,13 +21,9 @@ import net.corda.core.contracts.ScheduledStateRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.flatMap
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.join
|
||||
import net.corda.core.internal.until
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -47,18 +43,9 @@ import net.corda.nodeapi.internal.persistence.contextTransaction
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.apache.mina.util.ConcurrentHashSet
|
||||
import org.slf4j.Logger
|
||||
import java.io.Serializable
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CompletionStage
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.EmbeddedId
|
||||
@ -159,7 +146,7 @@ class NodeSchedulerService(private val clock: CordaClock,
|
||||
|
||||
@Column(name = "scheduled_at", nullable = false)
|
||||
var scheduledAt: Instant = Instant.now()
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
private class InnerState {
|
||||
var rescheduled: GuavaSettableFuture<Boolean>? = null
|
||||
|
@ -29,7 +29,6 @@ import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
||||
import java.io.Serializable
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
@ -96,7 +95,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
@Lob
|
||||
@Column(name = "identity_value", nullable = false)
|
||||
var identity: ByteArray = EMPTY_BYTE_ARRAY
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
@Entity
|
||||
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}named_identities")
|
||||
@ -107,7 +106,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
|
||||
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = true)
|
||||
var publicKeyHash: String? = ""
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
override val caCertStore: CertStore
|
||||
override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
|
||||
|
@ -21,7 +21,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.io.Serializable
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
@ -54,7 +53,7 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
||||
@Lob
|
||||
@Column(name = "private_key", nullable = false)
|
||||
var privateKey: ByteArray = EMPTY_BYTE_ARRAY
|
||||
) : Serializable {
|
||||
) {
|
||||
constructor(publicKey: PublicKey, privateKey: PrivateKey)
|
||||
: this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded)
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import net.corda.node.services.statemachine.DeduplicationId
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -161,7 +160,7 @@ class P2PMessageDeduplicator(private val database: CordaPersistence) {
|
||||
|
||||
@Column(name = "sequence_number", nullable = true)
|
||||
var seqNo: Long? = null
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
private data class MessageMeta(val insertionTime: Instant, val senderHash: String?, val senderSeqNo: Long?)
|
||||
|
||||
|
@ -46,7 +46,7 @@ class DBCheckpointStorage : CheckpointStorage {
|
||||
@Lob
|
||||
@Column(name = "checkpoint_value", nullable = false)
|
||||
var checkpoint: ByteArray = EMPTY_BYTE_ARRAY
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
override fun addCheckpoint(id: StateMachineRunId, checkpoint: SerializedBytes<Checkpoint>) {
|
||||
currentDBSession().saveOrUpdate(DBCheckpoint().apply {
|
||||
|
@ -23,7 +23,6 @@ import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import javax.persistence.Column
|
||||
@ -48,7 +47,7 @@ class DBTransactionMappingStorage(private val database: CordaPersistence) : Stat
|
||||
|
||||
@Column(name = "state_machine_run_id", length = 36, nullable = true)
|
||||
var stateMachineRunId: String? = ""
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
private companion object {
|
||||
fun createMap(): AppendOnlyPersistentMap<SecureHash, StateMachineRunId, DBTransactionMapping, String> {
|
||||
|
@ -18,11 +18,7 @@ import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.bufferUntilSubscribed
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -37,12 +33,7 @@ import net.corda.serialization.internal.CordaSerializationEncoding.SNAPPY
|
||||
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.Serializable
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Lob
|
||||
import javax.persistence.Table
|
||||
import javax.persistence.*
|
||||
|
||||
// cache value type to just store the immutable bits of a signed transaction plus conversion helpers
|
||||
typealias TxCacheValue = Pair<SerializedBytes<CoreTransaction>, List<TransactionSignature>>
|
||||
@ -62,7 +53,7 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers
|
||||
@Lob
|
||||
@Column(name = "transaction_value", nullable = false)
|
||||
var transaction: ByteArray = EMPTY_BYTE_ARRAY
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
private companion object {
|
||||
fun createTransactionsMap(maxSizeInBytes: Long)
|
||||
|
@ -30,11 +30,7 @@ import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||
import net.corda.core.node.services.vault.AttachmentSort
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializationToken
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SerializeAsTokenContext
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
|
||||
@ -48,22 +44,12 @@ import net.corda.nodeapi.internal.withContractsInJar
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.Serializable
|
||||
import java.nio.file.Paths
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.jar.JarInputStream
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import javax.persistence.CollectionTable
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.ElementCollection
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.ForeignKey
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Index
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.Lob
|
||||
import javax.persistence.Table
|
||||
import javax.persistence.*
|
||||
|
||||
/**
|
||||
* Stores attachments using Hibernate to database.
|
||||
@ -125,7 +111,7 @@ class NodeAttachmentService(
|
||||
@CollectionTable(name = "${NODE_DATABASE_PREFIX}attachments_contracts", joinColumns = [(JoinColumn(name = "att_id", referencedColumnName = "att_id"))],
|
||||
foreignKey = ForeignKey(name = "FK__ctr_class__attachments"))
|
||||
var contractClassNames: List<ContractClassName>? = null
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
@VisibleForTesting
|
||||
var checkAttachmentsOnLoad = true
|
||||
|
@ -19,7 +19,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import org.slf4j.Logger
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.Serializable
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
@ -45,7 +44,7 @@ class NodePropertiesPersistentStore(readPhysicalNodeId: () -> String, persistenc
|
||||
|
||||
@Column(name = "property_value", nullable = true)
|
||||
var value: String? = ""
|
||||
) : Serializable
|
||||
)
|
||||
}
|
||||
|
||||
private class FlowsDrainingModeOperationsImpl(readPhysicalNodeId: () -> String, private val persistence: CordaPersistence, logger: Logger) : FlowsDrainingModeOperations {
|
||||
|
@ -32,7 +32,6 @@ import net.corda.core.utilities.debug
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||
import java.io.Serializable
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
@ -49,7 +48,7 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl
|
||||
|
||||
@Column(name = "consuming_transaction_id", nullable = true)
|
||||
val consumingTxHash: String?
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
@Entity
|
||||
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_request_log")
|
||||
@ -72,7 +71,7 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl
|
||||
|
||||
@Column(name = "request_timestamp", nullable = false)
|
||||
var requestDate: Instant
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
@Entity
|
||||
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_states")
|
||||
|
@ -42,7 +42,6 @@ import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import java.io.Serializable
|
||||
import java.nio.file.Path
|
||||
import java.time.Clock
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@ -104,7 +103,7 @@ class RaftUniquenessProvider(
|
||||
var value: String? = "",
|
||||
@Column(name = "raft_log_index", nullable = false)
|
||||
var index: Long = 0
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
/** Directory storing the Raft log and state machine snapshots */
|
||||
private val storagePath: Path = transportConfiguration.baseDirectory
|
||||
|
@ -14,9 +14,8 @@ import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.UpgradedContract
|
||||
import net.corda.core.node.services.ContractUpgradeService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import net.corda.node.utilities.PersistentMap
|
||||
import java.io.Serializable
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
@ -34,7 +33,7 @@ class ContractUpgradeServiceImpl : ContractUpgradeService, SingletonSerializeAsT
|
||||
/** refers to the UpgradedContract class name*/
|
||||
@Column(name = "contract_class_name", nullable = true)
|
||||
var upgradedContractClassName: String? = ""
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
private companion object {
|
||||
fun createContractUpgradesMap(): PersistentMap<String, String, DBContractUpgrade, String> {
|
||||
|
@ -21,7 +21,6 @@ import net.corda.core.schemas.PersistentState
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import org.hibernate.annotations.Type
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.persistence.*
|
||||
@ -167,7 +166,7 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
|
||||
|
||||
@Column(name = "note", nullable = true)
|
||||
var note: String?
|
||||
) : Serializable {
|
||||
) {
|
||||
constructor(txId: String, note: String) : this(0, txId, note)
|
||||
}
|
||||
}
|
@ -180,7 +180,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) {
|
||||
|
||||
@Test
|
||||
fun `InputStream serialisation`() {
|
||||
val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() })
|
||||
val rubbish = ByteArray(12345) { (it * it * 0.12345).toByte() }
|
||||
val readRubbishStream: InputStream = rubbish.inputStream().serialize(factory, context).deserialize(factory, context)
|
||||
for (i in 0..12344) {
|
||||
assertEquals(rubbish[i], readRubbishStream.read().toByte())
|
||||
@ -218,7 +218,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) {
|
||||
|
||||
@Test
|
||||
fun `HashCheckingStream (de)serialize`() {
|
||||
val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() })
|
||||
val rubbish = ByteArray(12345) { (it * it * 0.12345).toByte() }
|
||||
val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream(
|
||||
SecureHash.sha256(rubbish),
|
||||
rubbish.size,
|
||||
|
@ -6,14 +6,12 @@ import net.corda.node.internal.configureDatabase
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import org.junit.After
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.Serializable
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
@ -270,7 +268,7 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) {
|
||||
|
||||
@Column(name = "value", length = 16)
|
||||
var value: String = ""
|
||||
) : Serializable
|
||||
)
|
||||
|
||||
class TestMap : AppendOnlyPersistentMap<Long, String, PersistentMapEntry, Long>(
|
||||
toPersistentEntityKey = { it },
|
||||
|
@ -30,7 +30,6 @@ import org.hibernate.annotations.Cascade
|
||||
import org.hibernate.annotations.CascadeType
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.io.Serializable
|
||||
import javax.persistence.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
@ -155,7 +154,7 @@ object TestSchema : MappedSchema(SchemaFamily::class.java, 1, setOf(Parent::clas
|
||||
@Suppress("unused")
|
||||
@Entity
|
||||
@Table(name = "children")
|
||||
class Child : Serializable {
|
||||
class Child {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@Column(name = "child_id", unique = true, nullable = false)
|
||||
|
@ -54,7 +54,7 @@ class TraderDemoTest : IntegrationTest() {
|
||||
startFlow<CashPaymentFlow>(),
|
||||
startFlow<CommercialPaperIssueFlow>(),
|
||||
all()))
|
||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||
driver(DriverParameters(startNodesInProcess = true, inMemoryDB = false, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||
val (nodeA, nodeB, bankNode) = listOf(
|
||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser)),
|
||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser)),
|
||||
@ -96,7 +96,7 @@ class TraderDemoTest : IntegrationTest() {
|
||||
|
||||
@Test
|
||||
fun `Tudor test`() {
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||
val demoUser = User("demo", "demo", setOf(startFlow<SellerFlow>(), all()))
|
||||
val bankUser = User("user1", "test", permissions = setOf(all()))
|
||||
val (nodeA, nodeB, bankNode) = listOf(
|
||||
|
@ -104,6 +104,9 @@ task jarFilter(type: JarFilterTask) {
|
||||
forRemove = [
|
||||
"co.paralleluniverse.fibers.Suspendable"
|
||||
]
|
||||
forSanitise = [
|
||||
"net.corda.core.DeleteForDJVM"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,7 +244,8 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
|
||||
jmxPolicy = defaultParameters.jmxPolicy,
|
||||
compatibilityZone = null,
|
||||
networkParameters = defaultParameters.networkParameters,
|
||||
notaryCustomOverrides = defaultParameters.notaryCustomOverrides
|
||||
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
|
||||
inMemoryDB = defaultParameters.inMemoryDB
|
||||
),
|
||||
coerce = { it },
|
||||
dsl = dsl,
|
||||
@ -277,6 +278,10 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
|
||||
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
|
||||
* empty as notaries are defined by [notarySpecs].
|
||||
* @property notaryCustomOverrides Extra settings that need to be passed to the notary.
|
||||
* @property initialiseSerialization Indicates whether to initialized the serialization subsystem.
|
||||
* @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however
|
||||
* the data is not persisted between node restarts). Has no effect if node is configured
|
||||
* in any way to use database other than H2.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
data class DriverParameters(
|
||||
@ -293,7 +298,8 @@ data class DriverParameters(
|
||||
val jmxPolicy: JmxPolicy = JmxPolicy(),
|
||||
val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
|
||||
val notaryCustomOverrides: Map<String, Any?> = emptyMap(),
|
||||
val initialiseSerialization: Boolean = true
|
||||
val initialiseSerialization: Boolean = true,
|
||||
val inMemoryDB: Boolean = true
|
||||
) {
|
||||
constructor(
|
||||
isDebug: Boolean,
|
||||
@ -322,6 +328,7 @@ data class DriverParameters(
|
||||
jmxPolicy,
|
||||
networkParameters,
|
||||
emptyMap(),
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
@ -338,7 +345,8 @@ data class DriverParameters(
|
||||
extraCordappPackagesToScan: List<String>,
|
||||
jmxPolicy: JmxPolicy,
|
||||
networkParameters: NetworkParameters,
|
||||
initialiseSerialization: Boolean
|
||||
initialiseSerialization: Boolean,
|
||||
inMemoryDB: Boolean
|
||||
) : this(
|
||||
isDebug,
|
||||
driverDirectory,
|
||||
@ -353,7 +361,8 @@ data class DriverParameters(
|
||||
jmxPolicy,
|
||||
networkParameters,
|
||||
emptyMap(),
|
||||
initialiseSerialization
|
||||
initialiseSerialization,
|
||||
inMemoryDB
|
||||
)
|
||||
|
||||
fun withIsDebug(isDebug: Boolean): DriverParameters = copy(isDebug = isDebug)
|
||||
@ -370,6 +379,7 @@ data class DriverParameters(
|
||||
fun withJmxPolicy(jmxPolicy: JmxPolicy): DriverParameters = copy(jmxPolicy = jmxPolicy)
|
||||
fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters)
|
||||
fun withNotaryCustomOverrides(notaryCustomOverrides: Map<String, Any?>): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides)
|
||||
fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB)
|
||||
|
||||
fun copy(
|
||||
isDebug: Boolean,
|
||||
|
@ -84,6 +84,7 @@ import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.concurrent.thread
|
||||
@ -103,7 +104,8 @@ class DriverDSLImpl(
|
||||
val notarySpecs: List<NotarySpec>,
|
||||
val compatibilityZone: CompatibilityZoneParams?,
|
||||
val networkParameters: NetworkParameters,
|
||||
val notaryCustomOverrides: Map<String, Any?>
|
||||
val notaryCustomOverrides: Map<String, Any?>,
|
||||
val inMemoryDB: Boolean
|
||||
) : InternalDriverDSL {
|
||||
|
||||
private var _executorService: ScheduledExecutorService? = null
|
||||
@ -122,6 +124,9 @@ class DriverDSLImpl(
|
||||
private lateinit var _notaries: CordaFuture<List<NotaryHandle>>
|
||||
override val notaryHandles: List<NotaryHandle> get() = _notaries.getOrThrow()
|
||||
|
||||
// While starting with inProcess mode, we need to have different names to avoid clashes
|
||||
private val inMemoryCounter = AtomicInteger()
|
||||
|
||||
interface Waitable {
|
||||
@Throws(InterruptedException::class)
|
||||
fun waitFor()
|
||||
@ -138,6 +143,16 @@ class DriverDSLImpl(
|
||||
|
||||
private val jolokiaJarPath: String by lazy { resolveJar(".*jolokia-jvm-.*-agent\\.jar$") }
|
||||
|
||||
private fun NodeConfig.checkAndOverrideForInMemoryDB(): NodeConfig = this.run {
|
||||
if (inMemoryDB && corda.dataSourceProperties.getProperty("dataSource.url").startsWith("jdbc:h2:")) {
|
||||
val jdbcUrl = "jdbc:h2:mem:persistence${inMemoryCounter.getAndIncrement()};DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=100"
|
||||
corda.dataSourceProperties.setProperty("dataSource.url", jdbcUrl)
|
||||
NodeConfig(typesafe = typesafe + mapOf("dataSourceProperties" to mapOf("dataSource.url" to jdbcUrl)), corda = corda)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveJar(jarNamePattern: String): String {
|
||||
return try {
|
||||
val cl = ClassLoader.getSystemClassLoader()
|
||||
@ -246,7 +261,7 @@ class DriverDSLImpl(
|
||||
baseDirectory = baseDirectory(name),
|
||||
allowMissingConfig = true,
|
||||
configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true)
|
||||
))
|
||||
)).checkAndOverrideForInMemoryDB()
|
||||
return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap)
|
||||
}
|
||||
|
||||
@ -264,7 +279,7 @@ class DriverDSLImpl(
|
||||
"adminAddress" to portAllocation.nextHostAndPort().toString()
|
||||
),
|
||||
"devMode" to false)
|
||||
))
|
||||
)).checkAndOverrideForInMemoryDB()
|
||||
|
||||
val versionInfo = VersionInfo(1, "1", "1", "1")
|
||||
config.corda.certificatesDirectory.createDirectories()
|
||||
@ -387,7 +402,7 @@ class DriverDSLImpl(
|
||||
configOverrides = rawConfig.toNodeOnly()
|
||||
)
|
||||
val cordaConfig = typesafe.parseAsNodeConfiguration()
|
||||
val config = NodeConfig(rawConfig, cordaConfig)
|
||||
val config = NodeConfig(rawConfig, cordaConfig).checkAndOverrideForInMemoryDB()
|
||||
return startNodeInternal(config, webAddress, null, "512m", localNetworkMap)
|
||||
}
|
||||
|
||||
@ -1064,7 +1079,8 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
|
||||
notarySpecs = defaultParameters.notarySpecs,
|
||||
compatibilityZone = null,
|
||||
networkParameters = defaultParameters.networkParameters,
|
||||
notaryCustomOverrides = defaultParameters.notaryCustomOverrides
|
||||
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
|
||||
inMemoryDB = defaultParameters.inMemoryDB
|
||||
)
|
||||
)
|
||||
val shutdownHook = addShutdownHook(driverDsl::shutdown)
|
||||
@ -1143,6 +1159,7 @@ fun <A> internalDriver(
|
||||
networkParameters: NetworkParameters = DriverParameters().networkParameters,
|
||||
compatibilityZone: CompatibilityZoneParams? = null,
|
||||
notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides,
|
||||
inMemoryDB: Boolean = DriverParameters().inMemoryDB,
|
||||
dsl: DriverDSLImpl.() -> A
|
||||
): A {
|
||||
return genericDriver(
|
||||
@ -1160,7 +1177,8 @@ fun <A> internalDriver(
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = compatibilityZone,
|
||||
networkParameters = networkParameters,
|
||||
notaryCustomOverrides = notaryCustomOverrides
|
||||
notaryCustomOverrides = notaryCustomOverrides,
|
||||
inMemoryDB = inMemoryDB
|
||||
),
|
||||
coerce = { it },
|
||||
dsl = dsl,
|
||||
|
@ -125,6 +125,7 @@ fun <A> rpcDriver(
|
||||
jmxPolicy: JmxPolicy = JmxPolicy(),
|
||||
networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
|
||||
notaryCustomOverrides: Map<String, Any?> = emptyMap(),
|
||||
inMemoryDB: Boolean = true,
|
||||
dsl: RPCDriverDSL.() -> A
|
||||
): A {
|
||||
return genericDriver(
|
||||
@ -143,7 +144,8 @@ fun <A> rpcDriver(
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = null,
|
||||
networkParameters = networkParameters,
|
||||
notaryCustomOverrides = notaryCustomOverrides
|
||||
notaryCustomOverrides = notaryCustomOverrides,
|
||||
inMemoryDB = inMemoryDB
|
||||
), externalTrace
|
||||
),
|
||||
coerce = { it },
|
||||
|
@ -194,7 +194,7 @@ class ExplorerSimulation(private val options: OptionSet) {
|
||||
for (ref in 0..1) {
|
||||
for ((currency, issuer) in issuers) {
|
||||
val amount = Amount(1_000_000, currency)
|
||||
issuer.startFlow(::CashIssueAndPaymentFlow, amount, OpaqueBytes(ByteArray(1, { ref.toByte() })),
|
||||
issuer.startFlow(::CashIssueAndPaymentFlow, amount, OpaqueBytes.of( ref.toByte() ),
|
||||
it, anonymous, notary).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,190 @@
|
||||
package net.corda.tools.shell;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.google.common.collect.Lists;
|
||||
import kotlin.Pair;
|
||||
import net.corda.client.jackson.JacksonSupport;
|
||||
import net.corda.core.contracts.Amount;
|
||||
import net.corda.core.crypto.SecureHash;
|
||||
import net.corda.core.flows.FlowException;
|
||||
import net.corda.core.flows.FlowLogic;
|
||||
import net.corda.core.flows.FlowSession;
|
||||
import net.corda.core.flows.StateMachineRunId;
|
||||
import net.corda.core.identity.CordaX500Name;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.internal.concurrent.CordaFutureImplKt;
|
||||
import net.corda.core.internal.concurrent.OpenFuture;
|
||||
import net.corda.core.messaging.FlowProgressHandleImpl;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
import net.corda.node.services.identity.InMemoryIdentityService;
|
||||
import net.corda.testing.core.TestIdentity;
|
||||
import net.corda.testing.internal.InternalTestConstantsKt;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.Test;
|
||||
import rx.Observable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class InteractiveShellJavaTest {
|
||||
private static TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
||||
|
||||
// should guarantee that FlowA will have synthetic method to access this field
|
||||
private static String synthetic = "synth";
|
||||
|
||||
abstract static class StringFlow extends FlowLogic<String> {
|
||||
abstract String getA();
|
||||
}
|
||||
|
||||
public static class FlowA extends StringFlow {
|
||||
|
||||
private String a;
|
||||
|
||||
public FlowA(String a) {
|
||||
if (!synthetic.isEmpty()) {
|
||||
this.a = a;
|
||||
}
|
||||
}
|
||||
|
||||
public FlowA(Integer b) {
|
||||
this(b.toString());
|
||||
}
|
||||
|
||||
public FlowA(Integer b, String c) {
|
||||
this(b.toString() + c);
|
||||
}
|
||||
|
||||
public FlowA(Amount<Currency> amount) {
|
||||
this(amount.toString());
|
||||
}
|
||||
|
||||
public FlowA(Pair<Amount<Currency>, SecureHash.SHA256> pair) {
|
||||
this(pair.toString());
|
||||
}
|
||||
|
||||
public FlowA(Party party) {
|
||||
this(party.getName().toString());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ProgressTracker getProgressTracker() {
|
||||
return new ProgressTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call() throws FlowException {
|
||||
return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getA() {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FlowB extends StringFlow {
|
||||
|
||||
private Party party;
|
||||
private String a;
|
||||
|
||||
public FlowB(Party party, String a) {
|
||||
this.party = party;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ProgressTracker getProgressTracker() {
|
||||
return new ProgressTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call() throws FlowException {
|
||||
FlowSession session = initiateFlow(party);
|
||||
|
||||
|
||||
Integer integer = session.receive(Integer.class).unwrap((i) -> {
|
||||
return i;
|
||||
});
|
||||
|
||||
return integer.toString();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
String getA() {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
private InMemoryIdentityService ids = new InMemoryIdentityService(Lists.newArrayList(megaCorp.getIdentity()), InternalTestConstantsKt.getDEV_ROOT_CA().getCertificate());
|
||||
|
||||
private ObjectMapper om = JacksonSupport.createInMemoryMapper(ids, new YAMLFactory());
|
||||
|
||||
private String output;
|
||||
|
||||
private void check(String input, String expected, Class<? extends StringFlow> flowClass) throws InteractiveShell.NoApplicableConstructor {
|
||||
InteractiveShell.INSTANCE.runFlowFromString((clazz, args) -> {
|
||||
|
||||
StringFlow instance = null;
|
||||
try {
|
||||
instance = (StringFlow)clazz.getConstructor(Arrays.stream(args).map(Object::getClass).toArray(Class[]::new)).newInstance(args);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
output = instance.getA();
|
||||
OpenFuture<String> future = CordaFutureImplKt.openFuture();
|
||||
future.set("ABC");
|
||||
return new FlowProgressHandleImpl(StateMachineRunId.Companion.createRandom(), future, Observable.just("Some string"));
|
||||
}, input, flowClass, om);
|
||||
assertEquals(input, expected, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flowStartSimple() throws InteractiveShell.NoApplicableConstructor {
|
||||
check("a: Hi there", "Hi there", FlowA.class);
|
||||
check("b: 12", "12", FlowA.class);
|
||||
check("b: 12, c: Yo", "12Yo", FlowA.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flowStartWithComplexTypes() throws InteractiveShell.NoApplicableConstructor {
|
||||
check("amount: £10", "10.00 GBP", FlowA.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flowStartWithNestedTypes() throws InteractiveShell.NoApplicableConstructor {
|
||||
check(
|
||||
"pair: { first: $100.12, second: df489807f81c8c8829e509e1bcb92e6692b9dd9d624b7456435cb2f51dc82587 }",
|
||||
"($100.12, df489807f81c8c8829e509e1bcb92e6692b9dd9d624b7456435cb2f51dc82587)",
|
||||
FlowA.class);
|
||||
}
|
||||
|
||||
@Test(expected = InteractiveShell.NoApplicableConstructor.class)
|
||||
public void flowStartNoArgs() throws InteractiveShell.NoApplicableConstructor {
|
||||
check("", "", FlowA.class);
|
||||
}
|
||||
|
||||
@Test(expected = InteractiveShell.NoApplicableConstructor.class)
|
||||
public void flowMissingParam() throws InteractiveShell.NoApplicableConstructor {
|
||||
check("c: Yo", "", FlowA.class);
|
||||
}
|
||||
|
||||
@Test(expected = InteractiveShell.NoApplicableConstructor.class)
|
||||
public void flowTooManyParams() throws InteractiveShell.NoApplicableConstructor {
|
||||
check("b: 12, c: Yo, d: Bar", "", FlowA.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void party() throws InteractiveShell.NoApplicableConstructor {
|
||||
check("party: \"" + megaCorp.getName() + "\"", megaCorp.getName().toString(), FlowA.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unwrapLambda() throws InteractiveShell.NoApplicableConstructor {
|
||||
check("party: \"" + megaCorp.getName() + "\", a: Bambam", "Bambam", FlowB.class);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user