mirror of
https://github.com/corda/corda.git
synced 2025-04-13 06:03:13 +00:00
Merge pull request #1280 from corda/chrisr3-os-merge
Merge up to e879de70f306af30a80ecbd125dab799aeeaf268.
This commit is contained in:
commit
1b05ccd397
@ -264,6 +264,9 @@ allprojects {
|
||||
force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
force "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
// Force dependencies to use the same version of Guava as Corda.
|
||||
force "com.google.guava:guava:$guava_version"
|
||||
|
||||
// Demand that everything uses our given version of Netty.
|
||||
eachDependency { details ->
|
||||
if (details.requested.group == 'io.netty' && details.requested.name.startsWith('netty-')) {
|
||||
|
@ -13,18 +13,19 @@ import org.objectweb.asm.Opcodes.ACC_SYNTHETIC
|
||||
import java.util.*
|
||||
|
||||
private const val DEFAULT_CONSTRUCTOR_MARKER = "ILkotlin/jvm/internal/DefaultConstructorMarker;"
|
||||
private const val DEFAULT_FUNCTION_MARKER = "ILjava/lang/Object;"
|
||||
private const val DUMMY_PASSES = 1
|
||||
|
||||
private val DECLARES_DEFAULT_VALUE_MASK: Int = DECLARES_DEFAULT_VALUE.toFlags(true).inv()
|
||||
|
||||
internal abstract class Element(val name: String, val descriptor: String) {
|
||||
abstract class Element(val name: String, val descriptor: String) {
|
||||
private var lifetime: Int = DUMMY_PASSES
|
||||
|
||||
open val isExpired: Boolean get() = --lifetime < 0
|
||||
}
|
||||
|
||||
|
||||
internal class MethodElement(name: String, descriptor: String, val access: Int = 0) : Element(name, descriptor) {
|
||||
class MethodElement(name: String, descriptor: String, val access: Int = 0) : Element(name, descriptor) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
@ -65,7 +66,7 @@ internal class MethodElement(name: String, descriptor: String, val access: Int =
|
||||
* A class cannot have two fields with the same name but different types. However,
|
||||
* it can define extension functions and properties.
|
||||
*/
|
||||
internal class FieldElement(name: String, descriptor: String = "?", val extension: String = "()") : Element(name, descriptor) {
|
||||
class FieldElement(name: String, descriptor: String = "?", val extension: String = "()") : Element(name, descriptor) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
@ -79,6 +80,30 @@ internal class FieldElement(name: String, descriptor: String = "?", val extensio
|
||||
|
||||
val String.extensionType: String get() = substring(0, 1 + indexOf(')'))
|
||||
|
||||
/**
|
||||
* Returns a fully-qualified class name as it would exist
|
||||
* in the byte-code, e.g. as "a/b/c/ClassName$Nested".
|
||||
*/
|
||||
fun NameResolver.getClassInternalName(idx: Int): String
|
||||
= getQualifiedClassName(idx).replace('.', '$')
|
||||
|
||||
/**
|
||||
* Construct the signatures of the synthetic methods that
|
||||
* Kotlin would create to handle default parameter values.
|
||||
*/
|
||||
fun String.toKotlinDefaultConstructor(): String {
|
||||
val closer = lastIndexOf(')')
|
||||
return substring(0, closer) + DEFAULT_CONSTRUCTOR_MARKER + substring(closer)
|
||||
}
|
||||
|
||||
fun String.toKotlinDefaultFunction(classDescriptor: String): String {
|
||||
val opener = indexOf('(')
|
||||
val closer = lastIndexOf(')')
|
||||
return (substring(0, opener) + "\$default("
|
||||
+ classDescriptor + substring(opener + 1, closer) + DEFAULT_FUNCTION_MARKER
|
||||
+ substring(closer))
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Kotlin getter/setter method data to [MethodElement] objects.
|
||||
*/
|
||||
@ -124,7 +149,7 @@ internal fun JvmProtoBuf.JvmPropertySignature.toFieldElement(property: ProtoBuf.
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites metadata for constructor parameters.
|
||||
* Rewrites metadata for function and constructor parameters.
|
||||
*/
|
||||
internal fun ProtoBuf.Constructor.Builder.updateValueParameters(
|
||||
updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter
|
||||
@ -135,6 +160,15 @@ internal fun ProtoBuf.Constructor.Builder.updateValueParameters(
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun ProtoBuf.Function.Builder.updateValueParameters(
|
||||
updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter
|
||||
): ProtoBuf.Function.Builder {
|
||||
for (idx in 0 until valueParameterList.size) {
|
||||
setValueParameter(idx, updater(valueParameterList[idx]))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun ProtoBuf.ValueParameter.clearDeclaresDefaultValue(): ProtoBuf.ValueParameter {
|
||||
return if (DECLARES_DEFAULT_VALUE.get(flags)) {
|
||||
toBuilder().setFlags(flags and DECLARES_DEFAULT_VALUE_MASK).build()
|
||||
@ -142,3 +176,6 @@ internal fun ProtoBuf.ValueParameter.clearDeclaresDefaultValue(): ProtoBuf.Value
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
internal val List<ProtoBuf.ValueParameter>.hasAnyDefaultValues
|
||||
get() = any { DECLARES_DEFAULT_VALUE.get(it.flags) }
|
||||
|
@ -22,7 +22,7 @@ class FilterTransformer private constructor (
|
||||
private val removeAnnotations: Set<String>,
|
||||
private val deleteAnnotations: Set<String>,
|
||||
private val stubAnnotations: Set<String>,
|
||||
private val unwantedClasses: MutableSet<String>,
|
||||
private val unwantedElements: UnwantedCache,
|
||||
private val unwantedFields: MutableSet<FieldElement>,
|
||||
private val deletedMethods: MutableSet<MethodElement>,
|
||||
private val stubbedMethods: MutableSet<MethodElement>
|
||||
@ -33,7 +33,7 @@ class FilterTransformer private constructor (
|
||||
removeAnnotations: Set<String>,
|
||||
deleteAnnotations: Set<String>,
|
||||
stubAnnotations: Set<String>,
|
||||
unwantedClasses: MutableSet<String>
|
||||
unwantedElements: UnwantedCache
|
||||
) : this(
|
||||
visitor = visitor,
|
||||
logger = logger,
|
||||
@ -41,7 +41,7 @@ class FilterTransformer private constructor (
|
||||
removeAnnotations = removeAnnotations,
|
||||
deleteAnnotations = deleteAnnotations,
|
||||
stubAnnotations = stubAnnotations,
|
||||
unwantedClasses = unwantedClasses,
|
||||
unwantedElements = unwantedElements,
|
||||
unwantedFields = mutableSetOf(),
|
||||
deletedMethods = mutableSetOf(),
|
||||
stubbedMethods = mutableSetOf()
|
||||
@ -57,7 +57,7 @@ class FilterTransformer private constructor (
|
||||
|| stubbedMethods.isNotEmpty()
|
||||
|| super.hasUnwantedElements
|
||||
|
||||
private fun isUnwantedClass(name: String): Boolean = unwantedClasses.contains(name)
|
||||
private fun isUnwantedClass(name: String): Boolean = unwantedElements.containsClass(name)
|
||||
private fun hasDeletedSyntheticMethod(name: String): Boolean = deletedMethods.any { method ->
|
||||
name.startsWith("$className\$${method.visibleName}\$")
|
||||
}
|
||||
@ -69,7 +69,7 @@ class FilterTransformer private constructor (
|
||||
removeAnnotations = removeAnnotations,
|
||||
deleteAnnotations = deleteAnnotations,
|
||||
stubAnnotations = stubAnnotations,
|
||||
unwantedClasses = unwantedClasses,
|
||||
unwantedElements = unwantedElements,
|
||||
unwantedFields = unwantedFields,
|
||||
deletedMethods = deletedMethods,
|
||||
stubbedMethods = stubbedMethods
|
||||
@ -86,7 +86,7 @@ class FilterTransformer private constructor (
|
||||
logger.info("- Removing annotation {}", descriptor)
|
||||
return null
|
||||
} else if (deleteAnnotations.contains(descriptor)) {
|
||||
if (unwantedClasses.add(className)) {
|
||||
if (unwantedElements.addClass(className)) {
|
||||
logger.info("- Identified class {} as unwanted", className)
|
||||
}
|
||||
}
|
||||
@ -110,6 +110,7 @@ class FilterTransformer private constructor (
|
||||
logger.debug("--- method ---> {}", method)
|
||||
if (deletedMethods.contains(method)) {
|
||||
logger.info("- Deleted method {}{}", method.name, method.descriptor)
|
||||
unwantedElements.addMethod(className, method)
|
||||
deletedMethods.remove(method)
|
||||
return null
|
||||
}
|
||||
@ -131,7 +132,7 @@ class FilterTransformer private constructor (
|
||||
override fun visitInnerClass(clsName: String, outerName: String?, innerName: String?, access: Int) {
|
||||
logger.debug("--- inner class {} [outer: {}, inner: {}]", clsName, outerName, innerName)
|
||||
if (isUnwantedClass || hasDeletedSyntheticMethod(clsName)) {
|
||||
if (unwantedClasses.add(clsName)) {
|
||||
if (unwantedElements.addClass(clsName)) {
|
||||
logger.info("- Deleted inner class {}", clsName)
|
||||
}
|
||||
} else if (isUnwantedClass(clsName)) {
|
||||
@ -143,8 +144,10 @@ class FilterTransformer private constructor (
|
||||
|
||||
override fun visitOuterClass(outerName: String, methodName: String?, methodDescriptor: String?) {
|
||||
logger.debug("--- outer class {} [enclosing method {},{}]", outerName, methodName, methodDescriptor)
|
||||
if (isUnwantedClass(outerName)) {
|
||||
logger.info("- Deleted reference to outer class {}", outerName)
|
||||
if (unwantedElements.containsMethod(outerName, methodName, methodDescriptor)) {
|
||||
if (unwantedElements.addClass(className)) {
|
||||
logger.info("- Identified class {} as unwanted by its outer class", className)
|
||||
}
|
||||
} else {
|
||||
super.visitOuterClass(outerName, methodName, methodDescriptor)
|
||||
}
|
||||
@ -180,8 +183,8 @@ class FilterTransformer private constructor (
|
||||
deletedFields = unwantedFields,
|
||||
deletedFunctions = partitioned[false] ?: emptyList(),
|
||||
deletedConstructors = partitioned[true] ?: emptyList(),
|
||||
deletedNestedClasses = unwantedClasses.filter { it.startsWith(prefix) }.map { it.drop(prefix.length) },
|
||||
deletedClasses = unwantedClasses,
|
||||
deletedNestedClasses = unwantedElements.classes.filter { it.startsWith(prefix) }.map { it.drop(prefix.length) },
|
||||
deletedClasses = unwantedElements.classes,
|
||||
handleExtraMethod = ::delete,
|
||||
d1 = d1,
|
||||
d2 = d2)
|
||||
|
@ -138,7 +138,7 @@ open class JarFilterTask : DefaultTask() {
|
||||
}
|
||||
|
||||
private inner class Filter(inFile: File) {
|
||||
private val unwantedClasses: MutableSet<String> = mutableSetOf()
|
||||
private val unwantedElements = UnwantedCache()
|
||||
private val source: Path = inFile.toPath()
|
||||
private val target: Path = toFiltered(inFile).toPath()
|
||||
|
||||
@ -251,7 +251,7 @@ open class JarFilterTask : DefaultTask() {
|
||||
removeAnnotations = descriptorsForRemove,
|
||||
deleteAnnotations = descriptorsForDelete,
|
||||
stubAnnotations = descriptorsForStub,
|
||||
unwantedClasses = unwantedClasses
|
||||
unwantedElements = unwantedElements
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -34,10 +34,11 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
parser: (InputStream, ExtensionRegistryLite) -> T
|
||||
) {
|
||||
private val stringTableTypes: StringTableTypes
|
||||
private val nameResolver: NameResolver
|
||||
protected val nameResolver: NameResolver
|
||||
protected val message: T
|
||||
|
||||
protected abstract val typeTable: TypeTable
|
||||
protected open val classDescriptor: String = ""
|
||||
protected open val classKind: ProtoBuf.Class.Kind? = null
|
||||
protected abstract val properties: MutableList<ProtoBuf.Property>
|
||||
protected abstract val functions: MutableList<ProtoBuf.Function>
|
||||
@ -78,7 +79,7 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < sealedSubclassNames.size) {
|
||||
val sealedSubclassName = nameResolver.getString(sealedSubclassNames[idx]).replace('.', '$')
|
||||
val sealedSubclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
|
||||
if (actualClasses.contains(sealedSubclassName)) {
|
||||
++idx
|
||||
} else {
|
||||
@ -93,15 +94,25 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
private fun filterFunctions(): Int {
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < functions.size) {
|
||||
val signature = JvmProtoBufUtil.getJvmMethodSignature(functions[idx], nameResolver, typeTable)
|
||||
if ((signature == null) || actualMethods.contains(signature)) {
|
||||
++idx
|
||||
} else {
|
||||
logger.info("-- removing method: {}", signature)
|
||||
functions.removeAt(idx)
|
||||
++count
|
||||
removed@ while (idx < functions.size) {
|
||||
val function = functions[idx]
|
||||
val signature = JvmProtoBufUtil.getJvmMethodSignature(function, nameResolver, typeTable)
|
||||
if (signature != null) {
|
||||
if (!actualMethods.contains(signature)) {
|
||||
logger.info("-- removing method: {}", signature)
|
||||
functions.removeAt(idx)
|
||||
++count
|
||||
continue@removed
|
||||
} else if (function.valueParameterList.hasAnyDefaultValues
|
||||
&& !actualMethods.contains(signature.toKotlinDefaultFunction(classDescriptor))) {
|
||||
logger.info("-- removing default parameter values: {}", signature)
|
||||
functions[idx] = function.toBuilder()
|
||||
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
|
||||
.build()
|
||||
++count
|
||||
}
|
||||
}
|
||||
++idx
|
||||
}
|
||||
return count
|
||||
}
|
||||
@ -109,15 +120,25 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
private fun filterConstructors(): Int {
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < constructors.size) {
|
||||
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructors[idx], nameResolver, typeTable)
|
||||
if ((signature == null) || actualMethods.contains(signature)) {
|
||||
++idx
|
||||
} else {
|
||||
logger.info("-- removing constructor: {}", signature)
|
||||
constructors.removeAt(idx)
|
||||
++count
|
||||
removed@ while (idx < constructors.size) {
|
||||
val constructor = constructors[idx]
|
||||
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructor, nameResolver, typeTable)
|
||||
if (signature != null) {
|
||||
if (!actualMethods.contains(signature)) {
|
||||
logger.info("-- removing constructor: {}", signature)
|
||||
constructors.removeAt(idx)
|
||||
++count
|
||||
continue@removed
|
||||
} else if (constructor.valueParameterList.hasAnyDefaultValues
|
||||
&& !actualMethods.contains(signature.toKotlinDefaultConstructor())) {
|
||||
logger.info("-- removing default parameter values: {}", signature)
|
||||
constructors[idx] = constructor.toBuilder()
|
||||
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
|
||||
.build()
|
||||
++count
|
||||
}
|
||||
}
|
||||
++idx
|
||||
}
|
||||
return count
|
||||
}
|
||||
@ -127,30 +148,32 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
var idx = 0
|
||||
removed@ while (idx < properties.size) {
|
||||
val property = properties[idx]
|
||||
val signature = property.getExtensionOrNull(propertySignature) ?: continue
|
||||
val field = signature.toFieldElement(property, nameResolver, typeTable)
|
||||
val getterMethod = signature.toGetter(nameResolver)
|
||||
val signature = property.getExtensionOrNull(propertySignature)
|
||||
if (signature != null) {
|
||||
val field = signature.toFieldElement(property, nameResolver, typeTable)
|
||||
val getterMethod = signature.toGetter(nameResolver)
|
||||
|
||||
/**
|
||||
* A property annotated with [JvmField] will use a field instead of a getter method.
|
||||
* But properties without [JvmField] will also usually have a backing field. So we only
|
||||
* remove a property that has either lost its getter method, or never had a getter method
|
||||
* and has lost its field.
|
||||
*
|
||||
* Having said that, we cannot remove [JvmField] properties from a companion object class
|
||||
* because these properties are implemented as static fields on the companion's host class.
|
||||
*/
|
||||
val isValidProperty = if (getterMethod == null) {
|
||||
actualFields.contains(field) || classKind == COMPANION_OBJECT
|
||||
} else {
|
||||
actualMethods.contains(getterMethod.name + getterMethod.descriptor)
|
||||
}
|
||||
/**
|
||||
* A property annotated with [JvmField] will use a field instead of a getter method.
|
||||
* But properties without [JvmField] will also usually have a backing field. So we only
|
||||
* remove a property that has either lost its getter method, or never had a getter method
|
||||
* and has lost its field.
|
||||
*
|
||||
* Having said that, we cannot remove [JvmField] properties from a companion object class
|
||||
* because these properties are implemented as static fields on the companion's host class.
|
||||
*/
|
||||
val isValidProperty = if (getterMethod == null) {
|
||||
actualFields.contains(field) || classKind == COMPANION_OBJECT
|
||||
} else {
|
||||
actualMethods.contains(getterMethod.signature)
|
||||
}
|
||||
|
||||
if (!isValidProperty) {
|
||||
logger.info("-- removing property: {},{}", field.name, field.descriptor)
|
||||
properties.removeAt(idx)
|
||||
++count
|
||||
continue@removed
|
||||
if (!isValidProperty) {
|
||||
logger.info("-- removing property: {},{}", field.name, field.descriptor)
|
||||
properties.removeAt(idx)
|
||||
++count
|
||||
continue@removed
|
||||
}
|
||||
}
|
||||
++idx
|
||||
}
|
||||
@ -196,6 +219,7 @@ internal class ClassMetaFixerTransformer(
|
||||
ProtoBuf.Class::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val classDescriptor = "L${nameResolver.getClassInternalName(message.fqName)};"
|
||||
override val classKind: ProtoBuf.Class.Kind = CLASS_KIND.get(message.flags)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
@ -204,18 +228,15 @@ internal class ClassMetaFixerTransformer(
|
||||
override val sealedSubclassNames= mutableList(message.sealedSubclassFqNameList)
|
||||
|
||||
override fun rebuild(): ProtoBuf.Class = message.toBuilder().apply {
|
||||
clearConstructor().addAllConstructor(constructors)
|
||||
clearFunction().addAllFunction(functions)
|
||||
|
||||
if (nestedClassNames.size != nestedClassNameCount) {
|
||||
clearNestedClassName().addAllNestedClassName(nestedClassNames)
|
||||
}
|
||||
if (sealedSubclassNames.size != sealedSubclassFqNameCount) {
|
||||
clearSealedSubclassFqName().addAllSealedSubclassFqName(sealedSubclassNames)
|
||||
}
|
||||
if (constructors.size != constructorCount) {
|
||||
clearConstructor().addAllConstructor(constructors)
|
||||
}
|
||||
if (functions.size != functionCount) {
|
||||
clearFunction().addAllFunction(functions)
|
||||
}
|
||||
if (properties.size != propertyCount) {
|
||||
clearProperty().addAllProperty(properties)
|
||||
}
|
||||
@ -248,9 +269,8 @@ internal class PackageMetaFixerTransformer(
|
||||
override val constructors = mutableListOf<ProtoBuf.Constructor>()
|
||||
|
||||
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
|
||||
if (functions.size != functionCount) {
|
||||
clearFunction().addAllFunction(functions)
|
||||
}
|
||||
clearFunction().addAllFunction(functions)
|
||||
|
||||
if (properties.size != propertyCount) {
|
||||
clearProperty().addAllProperty(properties)
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ internal abstract class MetadataTransformer<out T : MessageLite>(
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < sealedSubclassNames.size) {
|
||||
val subclassName = nameResolver.getString(sealedSubclassNames[idx]).replace('.', '$')
|
||||
val subclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
|
||||
if (deletedClasses.contains(subclassName)) {
|
||||
logger.info("-- removing sealed subclass: {}", subclassName)
|
||||
sealedSubclassNames.removeAt(idx)
|
||||
@ -248,7 +248,7 @@ internal class ClassMetadataTransformer(
|
||||
ProtoBuf.Class::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val className = nameResolver.getString(message.fqName)
|
||||
override val className = nameResolver.getClassInternalName(message.fqName)
|
||||
override val nestedClassNames = mutableList(message.nestedClassNameList)
|
||||
override val sealedSubclassNames = mutableList(message.sealedSubclassFqNameList)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
|
@ -0,0 +1,46 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import java.util.Collections.unmodifiableMap
|
||||
|
||||
/**
|
||||
* A persistent cache of all of the classes and methods that JarFilter has
|
||||
* removed. This cache belongs to the Gradle task itself and so is shared
|
||||
* by successive filter passes.
|
||||
*
|
||||
* The internal method cache is only required for those classes which are
|
||||
* being kept. When an entire class is declared as "unwanted", any entry
|
||||
* it may have in the method cache is removed.
|
||||
*/
|
||||
class UnwantedCache {
|
||||
private val _classes: MutableSet<String> = mutableSetOf()
|
||||
private val _classMethods: MutableMap<String, MutableSet<MethodElement>> = mutableMapOf()
|
||||
|
||||
val classes: Set<String> get() = _classes
|
||||
val classMethods: Map<String, Set<MethodElement>> get() = unmodifiableMap(_classMethods)
|
||||
|
||||
fun containsClass(className: String): Boolean = _classes.contains(className)
|
||||
|
||||
fun addClass(className: String): Boolean {
|
||||
return _classes.add(className).also { isAdded ->
|
||||
if (isAdded) {
|
||||
_classMethods.remove(className)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addMethod(className: String, method: MethodElement) {
|
||||
if (!containsClass(className)) {
|
||||
_classMethods.getOrPut(className) { mutableSetOf() }.add(method)
|
||||
}
|
||||
}
|
||||
|
||||
private fun containsMethod(className: String, method: MethodElement): Boolean {
|
||||
return _classMethods[className]?.contains(method) ?: false
|
||||
}
|
||||
|
||||
fun containsMethod(className: String, methodName: String?, methodDescriptor: String?): Boolean {
|
||||
return containsClass(className) ||
|
||||
(methodName != null && methodDescriptor != null && containsMethod(className, MethodElement(methodName, methodDescriptor)))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.isConstructor
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
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.test.assertFailsWith
|
||||
|
||||
class DeleteInnerLambdaTest {
|
||||
companion object {
|
||||
private const val LAMBDA_CLASS = "net.corda.gradle.HasInnerLambda"
|
||||
private const val SIZE = 64
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-inner-lambda")
|
||||
private val constructInt = isConstructor(LAMBDA_CLASS, Int::class)
|
||||
private val constructBytes = isConstructor(LAMBDA_CLASS, ByteArray::class)
|
||||
|
||||
private lateinit var sourceClasses: List<String>
|
||||
private lateinit var filteredClasses: List<String>
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
sourceClasses = testProject.sourceJar.getClassNames(LAMBDA_CLASS)
|
||||
filteredClasses = testProject.filteredJar.getClassNames(LAMBDA_CLASS)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test lambda class is deleted`() {
|
||||
assertThat(sourceClasses)
|
||||
.contains(LAMBDA_CLASS)
|
||||
.hasSize(2)
|
||||
assertThat(filteredClasses).containsExactly(LAMBDA_CLASS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test host class`() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(LAMBDA_CLASS).apply {
|
||||
getConstructor(Int::class.java).newInstance(SIZE).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
kotlin.constructors.also { ctors ->
|
||||
assertThat("<init>(Int) not found", ctors, hasItem(constructInt))
|
||||
assertThat("<init>(byte[]) not found", ctors, hasItem(constructBytes))
|
||||
}
|
||||
|
||||
getConstructor(ByteArray::class.java).newInstance(ByteArray(SIZE)).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(LAMBDA_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getConstructor(Int::class.java) }
|
||||
kotlin.constructors.also { ctors ->
|
||||
assertThat("<init>(Int) still exists", ctors, not(hasItem(constructInt)))
|
||||
assertThat("<init>(byte[]) not found", ctors, hasItem(constructBytes))
|
||||
}
|
||||
|
||||
getConstructor(ByteArray::class.java).newInstance(ByteArray(SIZE)).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,7 @@ class FieldRemovalTest {
|
||||
removeAnnotations = emptySet(),
|
||||
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
|
||||
stubAnnotations = emptySet(),
|
||||
unwantedClasses = mutableSetOf()
|
||||
unwantedElements = UnwantedCache()
|
||||
)
|
||||
}, COMPUTE_MAXS)
|
||||
return bytecode.toClass(type, asType)
|
||||
|
@ -0,0 +1,110 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.recodeMetadataFor
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import net.corda.gradle.jarfilter.matcher.isConstructor
|
||||
import net.corda.gradle.unwanted.HasAll
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
|
||||
class MetaFixConstructorDefaultParameterTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixConstructorDefaultParameterTest::class)
|
||||
private val primaryCon
|
||||
= isConstructor(WithConstructorParameters::class, Long::class, Int::class, String::class)
|
||||
private val secondaryCon
|
||||
= isConstructor(WithConstructorParameters::class, Char::class, String::class)
|
||||
|
||||
lateinit var sourceClass: Class<out HasAll>
|
||||
lateinit var fixedClass: Class<out HasAll>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
val bytecode = recodeMetadataFor<WithConstructorParameters, MetadataTemplate>()
|
||||
sourceClass = bytecode.toClass<WithConstructorParameters, HasAll>()
|
||||
fixedClass = bytecode.fixMetadata(logger, pathsOf(WithConstructorParameters::class))
|
||||
.toClass<WithConstructorParameters, HasAll>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test source constructor has optional parameters`() {
|
||||
with(sourceClass.kotlin.constructors) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("source primary constructor missing", this, hasItem(primaryCon))
|
||||
assertThat("source secondary constructor missing", this, hasItem(secondaryCon))
|
||||
}
|
||||
|
||||
val sourcePrimary = sourceClass.kotlin.primaryConstructor
|
||||
?: throw AssertionError("source primary constructor missing")
|
||||
sourcePrimary.call(BIG_NUMBER, NUMBER, MESSAGE).apply {
|
||||
assertThat(longData()).isEqualTo(BIG_NUMBER)
|
||||
assertThat(intData()).isEqualTo(NUMBER)
|
||||
assertThat(stringData()).isEqualTo(MESSAGE)
|
||||
}
|
||||
|
||||
val sourceSecondary = sourceClass.kotlin.constructors.firstOrNull { it != sourcePrimary }
|
||||
?: throw AssertionError("source secondary constructor missing")
|
||||
sourceSecondary.call('X', MESSAGE).apply {
|
||||
assertThat(stringData()).isEqualTo("X$MESSAGE")
|
||||
}
|
||||
|
||||
assertTrue("All source parameters should have defaults", sourcePrimary.hasAllOptionalParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed constructors exist`() {
|
||||
with(fixedClass.kotlin.constructors) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("fixed primary constructor missing", this, hasItem(primaryCon))
|
||||
assertThat("fixed secondary constructor missing", this, hasItem(secondaryCon))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed primary constructor has mandatory parameters`() {
|
||||
val fixedPrimary = fixedClass.kotlin.primaryConstructor
|
||||
?: throw AssertionError("fixed primary constructor missing")
|
||||
assertTrue("All fixed parameters should be mandatory", fixedPrimary.hasAllMandatoryParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed secondary constructor still has optional parameters`() {
|
||||
val fixedSecondary = (fixedClass.kotlin.constructors - fixedClass.kotlin.primaryConstructor).firstOrNull()
|
||||
?: throw AssertionError("fixed secondary constructor missing")
|
||||
assertTrue("Some fixed parameters should be optional", fixedSecondary.hasAnyOptionalParameters)
|
||||
}
|
||||
|
||||
class MetadataTemplate(
|
||||
private val longData: Long = 0,
|
||||
private val intData: Int = 0,
|
||||
private val message: String = DEFAULT_MESSAGE
|
||||
) : HasAll {
|
||||
@Suppress("UNUSED")
|
||||
constructor(prefix: Char, message: String = DEFAULT_MESSAGE) : this(message = prefix + message)
|
||||
|
||||
override fun longData(): Long = longData
|
||||
override fun intData(): Int = intData
|
||||
override fun stringData(): String = message
|
||||
}
|
||||
}
|
||||
|
||||
class WithConstructorParameters(
|
||||
private val longData: Long,
|
||||
private val intData: Int,
|
||||
private val message: String
|
||||
) : HasAll {
|
||||
@Suppress("UNUSED")
|
||||
constructor(prefix: Char, message: String = DEFAULT_MESSAGE) : this(0, 0, prefix + message)
|
||||
|
||||
override fun longData(): Long = longData
|
||||
override fun intData(): Int = intData
|
||||
override fun stringData(): String = message
|
||||
}
|
||||
|
@ -13,14 +13,8 @@ import kotlin.jvm.kotlin
|
||||
class MetaFixConstructorTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixConstructorTest::class)
|
||||
private val unwantedCon = isConstructor(
|
||||
returnType = WithConstructor::class,
|
||||
parameters = *arrayOf(Int::class, Long::class)
|
||||
)
|
||||
private val wantedCon = isConstructor(
|
||||
returnType = WithConstructor::class,
|
||||
parameters = *arrayOf(Long::class)
|
||||
)
|
||||
private val unwantedCon = isConstructor(WithConstructor::class, Int::class, Long::class)
|
||||
private val wantedCon = isConstructor(WithConstructor::class, Long::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -32,15 +26,19 @@ class MetaFixConstructorTest {
|
||||
// added to the metadata, and that the class is valid.
|
||||
val sourceObj = sourceClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
|
||||
assertEquals(BIG_NUMBER, sourceObj.longData())
|
||||
assertThat("<init>(Int,Long) not found", sourceClass.kotlin.constructors, hasItem(unwantedCon))
|
||||
assertThat("<init>(Long) not found", sourceClass.kotlin.constructors, hasItem(wantedCon))
|
||||
with(sourceClass.kotlin.constructors) {
|
||||
assertThat("<init>(Int,Long) not found", this, hasItem(unwantedCon))
|
||||
assertThat("<init>(Long) not found", this, hasItem(wantedCon))
|
||||
}
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithConstructor::class)).toClass<WithConstructor, HasLong>()
|
||||
val fixedObj = fixedClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
|
||||
assertEquals(BIG_NUMBER, fixedObj.longData())
|
||||
assertThat("<init>(Int,Long) still exists", fixedClass.kotlin.constructors, not(hasItem(unwantedCon)))
|
||||
assertThat("<init>(Long) not found", fixedClass.kotlin.constructors, hasItem(wantedCon))
|
||||
with(fixedClass.kotlin.constructors) {
|
||||
assertThat("<init>(Int,Long) still exists", this, not(hasItem(unwantedCon)))
|
||||
assertThat("<init>(Long) not found", this, hasItem(wantedCon))
|
||||
}
|
||||
}
|
||||
|
||||
class MetadataTemplate(private val longData: Long) : HasLong {
|
||||
|
@ -0,0 +1,96 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.recodeMetadataFor
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import net.corda.gradle.jarfilter.matcher.isFunction
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
|
||||
class MetaFixFunctionDefaultParameterTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixFunctionDefaultParameterTest::class)
|
||||
private val hasMandatoryParams
|
||||
= isFunction("hasMandatoryParams", String::class, Long::class, Int::class, String::class)
|
||||
private val hasOptionalParams
|
||||
= isFunction("hasOptionalParams", String::class, String::class)
|
||||
|
||||
lateinit var sourceClass: Class<out Any>
|
||||
lateinit var fixedClass: Class<out Any>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
val bytecode = recodeMetadataFor<WithFunctionParameters, MetadataTemplate>()
|
||||
sourceClass = bytecode.toClass<WithFunctionParameters, Any>()
|
||||
fixedClass = bytecode.fixMetadata(logger, pathsOf(WithFunctionParameters::class))
|
||||
.toClass<WithFunctionParameters, Any>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test source functions have default parameters`() {
|
||||
with(sourceClass.kotlin.declaredFunctions) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("source mandatory parameters missing", this, hasItem(hasMandatoryParams))
|
||||
assertThat("source optional parameters missing", this, hasItem(hasOptionalParams))
|
||||
}
|
||||
|
||||
val sourceUnwanted = sourceClass.kotlin.declaredFunctions.findOrFail("hasMandatoryParams")
|
||||
assertThat(sourceUnwanted.call(sourceClass.newInstance(), BIG_NUMBER, NUMBER, MESSAGE))
|
||||
.isEqualTo("Long: $BIG_NUMBER, Int: $NUMBER, String: $MESSAGE")
|
||||
|
||||
assertTrue("All source parameters should be optional", sourceUnwanted.hasAllOptionalParameters)
|
||||
|
||||
val sourceWanted = sourceClass.kotlin.declaredFunctions.findOrFail("hasOptionalParams")
|
||||
assertThat(sourceWanted.call(sourceClass.newInstance(), MESSAGE))
|
||||
.isEqualTo(MESSAGE)
|
||||
|
||||
assertTrue("All source parameters should be optional", sourceWanted.hasAllOptionalParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed functions exist`() {
|
||||
with(fixedClass.kotlin.declaredFunctions) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("fixed mandatory parameters missing", this, hasItem(hasMandatoryParams))
|
||||
assertThat("fixed optional parameters missing", this, hasItem(hasOptionalParams))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unwanted default parameters are removed`() {
|
||||
val fixedMandatory = fixedClass.kotlin.declaredFunctions.findOrFail("hasMandatoryParams")
|
||||
assertTrue("All fixed parameters should be mandatory", fixedMandatory.hasAllMandatoryParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test wanted default parameters are kept`() {
|
||||
val fixedOptional = fixedClass.kotlin.declaredFunctions.findOrFail("hasOptionalParams")
|
||||
assertTrue("All fixed parameters should be optional", fixedOptional.hasAllOptionalParameters)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
abstract class MetadataTemplate {
|
||||
abstract fun hasMandatoryParams(longData: Long = 0, intData: Int = 0, message: String = DEFAULT_MESSAGE): String
|
||||
abstract fun hasOptionalParams(message: String = DEFAULT_MESSAGE): String
|
||||
}
|
||||
|
||||
private fun <T> Iterable<KFunction<T>>.findOrFail(name: String): KFunction<T> {
|
||||
return find { it.name == name } ?: throw AssertionError("$name missing")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
class WithFunctionParameters {
|
||||
fun hasMandatoryParams(longData: Long, intData: Int, message: String): String {
|
||||
return "Long: $longData, Int: $intData, String: $message"
|
||||
}
|
||||
|
||||
fun hasOptionalParams(message: String = DEFAULT_MESSAGE): String = message
|
||||
}
|
@ -15,11 +15,7 @@ class MetaFixFunctionTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixFunctionTest::class)
|
||||
private val longData = isFunction("longData", Long::class)
|
||||
private val unwantedFun = isFunction(
|
||||
name = "unwantedFun",
|
||||
returnType = String::class,
|
||||
parameters = *arrayOf(String::class)
|
||||
)
|
||||
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -31,15 +27,19 @@ class MetaFixFunctionTest {
|
||||
// added to the metadata, and that the class is valid.
|
||||
val sourceObj = sourceClass.newInstance()
|
||||
assertEquals(BIG_NUMBER, sourceObj.longData())
|
||||
assertThat("unwantedFun(String) not found", sourceClass.kotlin.declaredFunctions, hasItem(unwantedFun))
|
||||
assertThat("longData not found", sourceClass.kotlin.declaredFunctions, hasItem(longData))
|
||||
with(sourceClass.kotlin.declaredFunctions) {
|
||||
assertThat("unwantedFun(String) not found", this, hasItem(unwantedFun))
|
||||
assertThat("longData not found", this, hasItem(longData))
|
||||
}
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithFunction::class)).toClass<WithFunction, HasLong>()
|
||||
val fixedObj = fixedClass.newInstance()
|
||||
assertEquals(BIG_NUMBER, fixedObj.longData())
|
||||
assertThat("unwantedFun(String) still exists", fixedClass.kotlin.declaredFunctions, not(hasItem(unwantedFun)))
|
||||
assertThat("longData not found", fixedClass.kotlin.declaredFunctions, hasItem(longData))
|
||||
with(fixedClass.kotlin.declaredFunctions) {
|
||||
assertThat("unwantedFun(String) still exists", this, not(hasItem(unwantedFun)))
|
||||
assertThat("longData not found", this, hasItem(longData))
|
||||
}
|
||||
}
|
||||
|
||||
class MetadataTemplate : HasLong {
|
||||
|
@ -0,0 +1,39 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.metadataAs
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
/**
|
||||
* These tests cannot actually "test" anything until Kotlin reflection
|
||||
* supports package metadata. Until then, we can only execute the code
|
||||
* paths to ensure they don't throw any exceptions.
|
||||
*/
|
||||
class MetaFixPackageDefaultParameterTest {
|
||||
companion object {
|
||||
private const val TEMPLATE_CLASS = "net.corda.gradle.jarfilter.template.PackageWithDefaultParameters"
|
||||
private const val DEFAULT_PARAMETERS_CLASS = "net.corda.gradle.jarfilter.PackageWithDefaultParameters"
|
||||
private val logger: Logger = StdOutLogging(MetaFixPackageDefaultParameterTest::class)
|
||||
|
||||
lateinit var sourceClass: Class<out Any>
|
||||
lateinit var fixedClass: Class<out Any>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
val defaultParametersClass = Class.forName(DEFAULT_PARAMETERS_CLASS)
|
||||
val bytecode = defaultParametersClass.metadataAs(Class.forName(TEMPLATE_CLASS))
|
||||
sourceClass = bytecode.toClass(defaultParametersClass, Any::class.java)
|
||||
fixedClass = bytecode.fixMetadata(logger, setOf(DEFAULT_PARAMETERS_CLASS)).toClass(sourceClass, Any::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test package functions`() {
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredFunctions }
|
||||
}
|
||||
}
|
@ -41,21 +41,21 @@ class MetaFixPackageTest {
|
||||
|
||||
@Test
|
||||
fun testPackageFunction() {
|
||||
assertFailsWith<UnsupportedOperationException> { sourceClass.kotlin.declaredFunctions }
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredFunctions }
|
||||
//assertThat("templateFun() not found", sourceClass.kotlin.declaredFunctions, hasItem(staticFun))
|
||||
//assertThat("templateFun() still exists", fixedClass.kotlin.declaredFunctions, not(hasItem(staticFun)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageVal() {
|
||||
assertFailsWith<UnsupportedOperationException> { sourceClass.kotlin.declaredMembers }
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredMembers }
|
||||
//assertThat("templateVal not found", sourceClass.kotlin.declaredMembers, hasItem(staticVal))
|
||||
//assertThat("templateVal still exists", fixedClass.kotlin.declaredMembers, not(hasItem(staticVal)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageVar() {
|
||||
assertFailsWith<UnsupportedOperationException> { sourceClass.kotlin.declaredMembers }
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredMembers }
|
||||
//assertThat("templateVar not found", sourceClass.kotlin.declaredMembers, hasItem(staticVar))
|
||||
//assertThat("templateVar still exists", fixedClass.kotlin.declaredMembers, not(hasItem(staticVar)))
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
@file:JvmName("PackageWithDefaultParameters")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
/**
|
||||
* Example package functions, one with default parameter values and one without.
|
||||
* We will rewrite this class's metadata so that it expects both functions to
|
||||
* have default parameter values, and then ask the [MetaFixerTask] to fix it.
|
||||
*/
|
||||
fun hasDefaultParameters(intData: Int=0, message: String=DEFAULT_MESSAGE): String = "$message: intData=$intData"
|
||||
|
||||
fun hasMandatoryParameters(longData: Long, message: String): String = "$message: longData=$longData"
|
@ -34,7 +34,7 @@ class StaticFieldRemovalTest {
|
||||
removeAnnotations = emptySet(),
|
||||
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
|
||||
stubAnnotations = emptySet(),
|
||||
unwantedClasses = mutableSetOf()
|
||||
unwantedElements = UnwantedCache()
|
||||
)
|
||||
}, COMPUTE_MAXS)
|
||||
return bytecode.toClass(type, asType)
|
||||
|
@ -0,0 +1,54 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class UnwantedCacheTest {
|
||||
private companion object {
|
||||
private const val CLASS_NAME = "org.testing.MyClass"
|
||||
private const val LONG_ARG = "(J)V"
|
||||
private const val NO_ARG = "()V"
|
||||
}
|
||||
|
||||
private lateinit var cache: UnwantedCache
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
cache = UnwantedCache()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyCache() {
|
||||
assertFalse(cache.containsClass(CLASS_NAME))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingClass() {
|
||||
cache.addClass(CLASS_NAME)
|
||||
assertTrue(cache.containsClass(CLASS_NAME))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingMethod() {
|
||||
cache.addMethod(CLASS_NAME, MethodElement("<init>", LONG_ARG))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", LONG_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "destroy", LONG_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "nonsense", null))
|
||||
assertFalse(cache.containsClass(CLASS_NAME))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingMethodFollowedByClass() {
|
||||
cache.addMethod(CLASS_NAME, MethodElement("<init>", LONG_ARG))
|
||||
cache.addClass(CLASS_NAME)
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", LONG_ARG))
|
||||
assertEquals(0, cache.classMethods.size)
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import java.util.zip.ZipFile
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.valueParameters
|
||||
|
||||
const val DEFAULT_MESSAGE = "<default-value>"
|
||||
const val MESSAGE = "Goodbye, Cruel World!"
|
||||
@ -67,8 +68,17 @@ fun arrayOfJunk(size: Int) = ByteArray(size).apply {
|
||||
}
|
||||
}
|
||||
|
||||
val KFunction<*>.hasAnyOptionalParameters: Boolean
|
||||
get() = valueParameters.any(KParameter::isOptional)
|
||||
|
||||
val KFunction<*>.hasAllOptionalParameters: Boolean
|
||||
get() = valueParameters.all(KParameter::isOptional)
|
||||
|
||||
val KFunction<*>.hasAllMandatoryParameters: Boolean
|
||||
get() = valueParameters.none(KParameter::isOptional)
|
||||
|
||||
val <T : Any> KClass<T>.noArgConstructor: KFunction<T>?
|
||||
get() = constructors.firstOrNull { it.parameters.all(KParameter::isOptional) }
|
||||
get() = constructors.firstOrNull(KFunction<*>::hasAllOptionalParameters)
|
||||
|
||||
@Throws(MalformedURLException::class)
|
||||
fun classLoaderFor(jar: Path) = URLClassLoader(arrayOf(jar.toUri().toURL()), classLoader)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.gradle.jarfilter.asm
|
||||
|
||||
import net.corda.gradle.jarfilter.MetadataTransformer
|
||||
import net.corda.gradle.jarfilter.getClassInternalName
|
||||
import net.corda.gradle.jarfilter.toPackageFormat
|
||||
import net.corda.gradle.jarfilter.mutableList
|
||||
import org.gradle.api.logging.Logger
|
||||
@ -24,7 +25,7 @@ internal class ClassMetadata(
|
||||
ProtoBuf.Class::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val className = nameResolver.getString(message.fqName)
|
||||
override val className = nameResolver.getClassInternalName(message.fqName)
|
||||
override val nestedClassNames = mutableList(message.nestedClassNameList)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
@ -35,13 +36,13 @@ internal class ClassMetadata(
|
||||
override fun rebuild(): ProtoBuf.Class = message
|
||||
|
||||
val sealedSubclasses: List<String> = sealedSubclassNames.map {
|
||||
// Transform "a/b/c/BaseName.SubclassName" -> "a.b.c.BaseName$SubclassName"
|
||||
nameResolver.getString(it).replace('.', '$').toPackageFormat }.toList()
|
||||
// Transform "a/b/c/BaseName$SubclassName" -> "a.b.c.BaseName$SubclassName"
|
||||
nameResolver.getClassInternalName(it).toPackageFormat }.toList()
|
||||
|
||||
val nestedClasses: List<String>
|
||||
|
||||
init {
|
||||
val internalClassName = className.toPackageFormat
|
||||
nestedClasses = nestedClassNames.map { "$internalClassName\$${nameResolver.getString(it)}" }.toList()
|
||||
nestedClasses = nestedClassNames.map { "$internalClassName\$${nameResolver.getClassInternalName(it)}" }.toList()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
@file:JvmName("PackageWithDefaultParameters")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter.template
|
||||
|
||||
import net.corda.gradle.jarfilter.DEFAULT_MESSAGE
|
||||
|
||||
fun hasDefaultParameters(intData: Int=0, message: String=DEFAULT_MESSAGE): String = "$message: intData=$intData"
|
||||
|
||||
fun hasMandatoryParameters(longData: Long=0, message: String=DEFAULT_MESSAGE): String = "$message: longData=$longData"
|
@ -0,0 +1,33 @@
|
||||
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/delete-inner-lambda/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'delete-inner-lambda'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
|
||||
class HasInnerLambda(private val bytes: ByteArray) : HasInt {
|
||||
@DeleteMe
|
||||
constructor(size: Int) : this(ZeroArray { size }.bytes)
|
||||
|
||||
override fun intData() = bytes.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Do NOT inline this lambda!
|
||||
*/
|
||||
class ZeroArray(initialSize: () -> Int) {
|
||||
val bytes: ByteArray = ByteArray(initialSize()) { 0 }
|
||||
}
|
@ -24,7 +24,9 @@ dependencies {
|
||||
compile project(':core')
|
||||
|
||||
// Quasar, for suspendable fibres.
|
||||
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
|
||||
compileOnly("$quasar_group:quasar-core:$quasar_version:jdk8") {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
testCompile "junit:junit:$junit_version"
|
||||
|
@ -8,7 +8,7 @@
|
||||
# Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
#
|
||||
|
||||
gradlePluginsVersion=4.0.25
|
||||
gradlePluginsVersion=4.0.26
|
||||
kotlinVersion=1.2.51
|
||||
platformVersion=4
|
||||
guavaVersion=25.1-jre
|
||||
|
@ -82,7 +82,9 @@ dependencies {
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
// Quasar, for suspendable fibres.
|
||||
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
|
||||
compileOnly("$quasar_group:quasar-core:$quasar_version:jdk8") {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
// Thread safety annotations
|
||||
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||
|
@ -15,6 +15,7 @@ import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
// DOCSTART FlowAsyncOperation
|
||||
/**
|
||||
* Interface for arbitrary operations that can be invoked in a flow asynchronously - the flow will suspend until the
|
||||
* operation completes. Operation parameters are expected to be injected via constructor.
|
||||
@ -24,10 +25,13 @@ interface FlowAsyncOperation<R : Any> {
|
||||
/** Performs the operation in a non-blocking fashion. */
|
||||
fun execute(): CordaFuture<R>
|
||||
}
|
||||
// DOCEND FlowAsyncOperation
|
||||
|
||||
// DOCSTART executeAsync
|
||||
/** Executes the specified [operation] and suspends until operation completion. */
|
||||
@Suspendable
|
||||
fun <T, R : Any> FlowLogic<T>.executeAsync(operation: FlowAsyncOperation<R>, maySkipCheckpoint: Boolean = false): R {
|
||||
val request = FlowIORequest.ExecuteAsyncOperation(operation)
|
||||
return stateMachine.suspend(request, maySkipCheckpoint)
|
||||
}
|
||||
// DOCEND executeAsync
|
||||
|
@ -19,6 +19,7 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import java.time.Instant
|
||||
|
||||
// DOCSTART FlowIORequest
|
||||
/**
|
||||
* A [FlowIORequest] represents an IO request of a flow when it suspends. It is persisted in checkpoints.
|
||||
*/
|
||||
@ -103,3 +104,4 @@ sealed class FlowIORequest<out R : Any> {
|
||||
// TODO: consider using an empty FlowAsyncOperation instead
|
||||
object ForceCheckpoint : FlowIORequest<Unit>()
|
||||
}
|
||||
// DOCSEND FlowIORequest
|
||||
|
@ -15,6 +15,8 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNull
|
||||
|
||||
import java.lang.Character.MIN_VALUE as NULLCHAR
|
||||
|
||||
class CordaX500NameTest {
|
||||
@Test
|
||||
fun `service name with organisational unit`() {
|
||||
@ -70,17 +72,128 @@ class CordaX500NameTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects name with wrong organisation name format`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects name with unsupported attribute`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=Bank A, L=New York, C=US, SN=blah")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects organisation (but not other attributes) with non-latin letters`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=Bཛྷa, L=New York, C=DE, OU=Org Unit, CN=Service Name")
|
||||
}
|
||||
// doesn't throw
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("Bཛྷa")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `organisation (but not other attributes) must have at least two letters`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=B, L=New York, C=DE, OU=Org Unit, CN=Service Name")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=, L=New York, C=DE, OU=Org Unit, CN=Service Name")
|
||||
}
|
||||
// doesn't throw
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("B")
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts attributes starting with lower case letter`() {
|
||||
CordaX500Name.parse("O=bank A, L=New York, C=DE, OU=Org Unit, CN=Service Name")
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("bank")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts attributes starting with numeric character`() {
|
||||
CordaX500Name.parse("O=8Bank A, L=New York, C=DE, OU=Org Unit, CN=Service Name")
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("8bank")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts attributes with leading whitespace`() {
|
||||
CordaX500Name.parse("O= VALID, L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
validateLocalityAndOrganisationalUnitAndCommonName(" VALID")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts attributes with trailing whitespace`() {
|
||||
CordaX500Name.parse("O=VALID , L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("VALID ")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects attributes with comma`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=IN,VALID, L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
}
|
||||
checkLocalityAndOrganisationalUnitAndCommonNameReject("IN,VALID")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts org with equals sign`() {
|
||||
CordaX500Name.parse("O=IN=VALID, L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts organisation with dollar sign`() {
|
||||
CordaX500Name.parse("O=VA\$LID, L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("VA\$LID")
|
||||
}
|
||||
@Test
|
||||
fun `rejects attributes with double quotation mark`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=IN\"VALID, L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
}
|
||||
checkLocalityAndOrganisationalUnitAndCommonNameReject("IN\"VALID")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts organisation with single quotation mark`() {
|
||||
CordaX500Name.parse("O=VA'LID, L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("VA'LID")
|
||||
}
|
||||
@Test
|
||||
fun `rejects organisation with backslash`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=IN\\VALID, L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
}
|
||||
checkLocalityAndOrganisationalUnitAndCommonNameReject("IN\\VALID")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects double spacing only in the organisation attribute`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=IN VALID , L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
}
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("VA LID")
|
||||
}
|
||||
@Test
|
||||
fun `rejects organisation (but not other attributes) containing the null character`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=IN${NULLCHAR}VALID , L=VALID, C=DE, OU=VALID, CN=VALID")
|
||||
}
|
||||
validateLocalityAndOrganisationalUnitAndCommonName("VA${NULLCHAR}LID")
|
||||
}
|
||||
|
||||
fun checkLocalityAndOrganisationalUnitAndCommonNameReject(invalid: String) {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=VALID, L=${invalid}, C=DE, OU=VALID, CN=VALID")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=VALID, L=VALID, C=DE, OU=${invalid}, CN=VALID")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
CordaX500Name.parse("O=VALID, L=VALID, C=DE, OU=VALID, CN=${invalid}")
|
||||
}
|
||||
}
|
||||
|
||||
fun validateLocalityAndOrganisationalUnitAndCommonName(valid: String) {
|
||||
CordaX500Name.parse("O=VALID, L=${valid}, C=DE, OU=VALID, CN=VALID")
|
||||
CordaX500Name.parse("O=VALID, L=VALID, C=DE, OU=${valid}, CN=VALID")
|
||||
CordaX500Name.parse("O=VALID, L=VALID, C=DE, OU=VALID, CN=${valid}")
|
||||
}
|
||||
}
|
||||
|
257
docs/source/contributing-flow-state-machines.rst
Normal file
257
docs/source/contributing-flow-state-machines.rst
Normal file
@ -0,0 +1,257 @@
|
||||
.. highlight:: kotlin
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||
|
||||
How to extend the state machine
|
||||
===============================
|
||||
|
||||
This article explains how to extend the state machine code that underlies flow execution. It is intended for Corda
|
||||
contributors.
|
||||
|
||||
How to add suspending operations
|
||||
--------------------------------
|
||||
|
||||
To add a suspending operation for a simple request-response type function that perhaps involves some external IO we can
|
||||
use the internal ``FlowAsyncOperation`` interface.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/internal/FlowAsyncOperation.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART FlowAsyncOperation
|
||||
:end-before: DOCEND FlowAsyncOperation
|
||||
|
||||
Let's imagine we want to add a suspending operation that takes two integers and returns their sum. To do this we
|
||||
implement ``FlowAsyncOperation``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/flowstatemachines/TutorialFlowAsyncOperation.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART SummingOperation
|
||||
:end-before: DOCEND SummingOperation
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/SummingOperation.java
|
||||
:language: java
|
||||
:start-after: DOCSTART SummingOperation
|
||||
:end-before: DOCEND SummingOperation
|
||||
|
||||
As we can see the constructor of ``SummingOperation`` takes the two numbers, and the ``execute`` function simply returns
|
||||
a future that is immediately completed by the result of summing the numbers. Note how we don't use ``@Suspendable`` on
|
||||
``execute``, this is because we'll never suspend inside this function, the suspension will happen before we're calling
|
||||
it.
|
||||
|
||||
Note also how the input numbers are stored in the class as fields. This is important, because in the flow's checkpoint
|
||||
we'll store an instance of this class whenever we're suspending on such an operation. If the node fails or restarts
|
||||
while the operation is underway this class will be deserialized from the checkpoint and ``execute`` will be called
|
||||
again.
|
||||
|
||||
Now we can use the internal function ``executeAsync`` to execute this operation from a flow.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/internal/FlowAsyncOperation.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART executeAsync
|
||||
:end-before: DOCEND executeAsync
|
||||
|
||||
It simply takes a ``FlowAsyncOperation`` and an optional flag we don't care about for now. We can use this function in a
|
||||
flow:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/flowstatemachines/TutorialFlowAsyncOperation.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART ExampleSummingFlow
|
||||
:end-before: DOCEND ExampleSummingFlow
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/ExampleSummingFlow.java
|
||||
:language: java
|
||||
:start-after: DOCSTART ExampleSummingFlow
|
||||
:end-before: DOCEND ExampleSummingFlow
|
||||
|
||||
That's it! Obviously this is a mostly useless example, but this is the basic code structure one could extend for heavier
|
||||
computations/other IO. For example the function could call into a ``CordaService`` or something similar. One thing to
|
||||
note is that the operation executed in ``execute`` must be redoable(= "idempotent") in case the node fails before the
|
||||
next checkpoint is committed.
|
||||
|
||||
How to test
|
||||
-----------
|
||||
|
||||
The recommended way to test flows and the state machine is using the Driver DSL. This ensures that you will test your
|
||||
flow with a full node.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/integration-test/kotlin/net/corda/docs/TutorialFlowAsyncOperationTest.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART summingWorks
|
||||
:end-before: DOCEND summingWorks
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/integration-test/java/net/corda/docs/java/TutorialFlowAsyncOperationTest.java
|
||||
:language: java
|
||||
:start-after: DOCSTART summingWorks
|
||||
:end-before: DOCEND summingWorks
|
||||
|
||||
The above will spin up a node and run our example flow.
|
||||
|
||||
How to debug issues
|
||||
-------------------
|
||||
|
||||
Let's assume we made a mistake in our summing operation:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/flowstatemachines/TutorialFlowAsyncOperation.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART SummingOperationThrowing
|
||||
:end-before: DOCEND SummingOperationThrowing
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/SummingOperationThrowing.java
|
||||
:language: java
|
||||
:start-after: DOCSTART SummingOperationThrowing
|
||||
:end-before: DOCEND SummingOperationThrowing
|
||||
|
||||
The operation now throws a rude exception. If we modify the example flow to use this and run the same test we will get
|
||||
a lot of logs about the error condition (as we are in dev mode). The interesting bit looks like this:
|
||||
|
||||
.. parsed-literal::
|
||||
[WARN ] 18:38:52,613 [Node thread-1] (DumpHistoryOnErrorInterceptor.kt:39) interceptors.DumpHistoryOnErrorInterceptor.executeTransition - Flow [03ab886e-3fd3-4667-b944-ab6a3b1f90a7] errored, dumping all transitions:
|
||||
|
||||
--- Transition of flow [03ab886e-3fd3-4667-b944-ab6a3b1f90a7] ---
|
||||
Timestamp: 2018-06-01T17:38:52.426Z
|
||||
Event: DoRemainingWork
|
||||
Actions:
|
||||
CreateTransaction
|
||||
PersistCheckpoint(id=[03ab886e-3fd3-4667-b944-ab6a3b1f90a7], checkpoint=Checkpoint(invocationContext=InvocationContext(origin=RPC(actor=Actor(id=Id(value=aliceUser), serviceId=AuthServiceId(value=NODE_CONFIG), owningLegalIdentity=O=Alice Corp, L=Madrid, C=ES)), trace=Trace(invocationId=26bcf0c3-f1d8-4098-a52d-3780f4095b7a, timestamp: 2018-06-01T17:38:52.234Z, entityType: Invocation, sessionId=393d1175-3bb1-4eb1-bff0-6ba317851260, timestamp: 2018-06-01T17:38:52.169Z, entityType: Session), actor=Actor(id=Id(value=aliceUser), serviceId=AuthServiceId(value=NODE_CONFIG), owningLegalIdentity=O=Alice Corp, L=Madrid, C=ES), externalTrace=null, impersonatedActor=null), ourIdentity=O=Alice Corp, L=Madrid, C=ES, sessions={}, subFlowStack=[Inlined(flowClass=class net.corda.docs.tutorial.flowstatemachines.ExampleSummingFlow, subFlowVersion=CorDappFlow(platformVersion=1, corDappName=net.corda.docs-c6816652-f975-4fb2-aa09-ef1dddea19b3, corDappHash=F4012397D8CF97926B5998E046DBCE16D497318BB87DCED66313912D4B303BB7))], flowState=Unstarted(flowStart=Explicit, frozenFlowLogic=74BA62EC5821EBD4FC4CBE129843F9ED6509DB37E6E3C8F85E3F7A8D84083500), errorState=Clean, numberOfSuspends=0, deduplicationSeed=03ab886e-3fd3-4667-b944-ab6a3b1f90a7))
|
||||
PersistDeduplicationFacts(deduplicationHandlers=[net.corda.node.internal.FlowStarterImpl$startFlow$startFlowEvent$1@69326343])
|
||||
CommitTransaction
|
||||
AcknowledgeMessages(deduplicationHandlers=[net.corda.node.internal.FlowStarterImpl$startFlow$startFlowEvent$1@69326343])
|
||||
SignalFlowHasStarted(flowId=[03ab886e-3fd3-4667-b944-ab6a3b1f90a7])
|
||||
CreateTransaction
|
||||
Continuation: Resume(result=null)
|
||||
Diff between previous and next state:
|
||||
isAnyCheckpointPersisted:
|
||||
false
|
||||
true
|
||||
pendingDeduplicationHandlers:
|
||||
[net.corda.node.internal.FlowStarterImpl$startFlow$startFlowEvent$1@69326343]
|
||||
[]
|
||||
isFlowResumed:
|
||||
false
|
||||
true
|
||||
|
||||
|
||||
--- Transition of flow [03ab886e-3fd3-4667-b944-ab6a3b1f90a7] ---
|
||||
Timestamp: 2018-06-01T17:38:52.487Z
|
||||
Event: Suspend(ioRequest=ExecuteAsyncOperation(operation=net.corda.docs.tutorial.flowstatemachines.SummingOperationThrowing@40f4c23d), maySkipCheckpoint=false, fiber=15EC69204562BB396846768169AD4A339569D97AE841D805C230C513A8BA5BDE, )
|
||||
Actions:
|
||||
PersistCheckpoint(id=[03ab886e-3fd3-4667-b944-ab6a3b1f90a7], checkpoint=Checkpoint(invocationContext=InvocationContext(origin=RPC(actor=Actor(id=Id(value=aliceUser), serviceId=AuthServiceId(value=NODE_CONFIG), owningLegalIdentity=O=Alice Corp, L=Madrid, C=ES)), trace=Trace(invocationId=26bcf0c3-f1d8-4098-a52d-3780f4095b7a, timestamp: 2018-06-01T17:38:52.234Z, entityType: Invocation, sessionId=393d1175-3bb1-4eb1-bff0-6ba317851260, timestamp: 2018-06-01T17:38:52.169Z, entityType: Session), actor=Actor(id=Id(value=aliceUser), serviceId=AuthServiceId(value=NODE_CONFIG), owningLegalIdentity=O=Alice Corp, L=Madrid, C=ES), externalTrace=null, impersonatedActor=null), ourIdentity=O=Alice Corp, L=Madrid, C=ES, sessions={}, subFlowStack=[Inlined(flowClass=class net.corda.docs.tutorial.flowstatemachines.ExampleSummingFlow, subFlowVersion=CorDappFlow(platformVersion=1, corDappName=net.corda.docs-c6816652-f975-4fb2-aa09-ef1dddea19b3, corDappHash=F4012397D8CF97926B5998E046DBCE16D497318BB87DCED66313912D4B303BB7))], flowState=Started(flowIORequest=ExecuteAsyncOperation(operation=net.corda.docs.tutorial.flowstatemachines.SummingOperationThrowing@40f4c23d), frozenFiber=15EC69204562BB396846768169AD4A339569D97AE841D805C230C513A8BA5BDE), errorState=Clean, numberOfSuspends=1, deduplicationSeed=03ab886e-3fd3-4667-b944-ab6a3b1f90a7))
|
||||
PersistDeduplicationFacts(deduplicationHandlers=[])
|
||||
CommitTransaction
|
||||
AcknowledgeMessages(deduplicationHandlers=[])
|
||||
ScheduleEvent(event=DoRemainingWork)
|
||||
Continuation: ProcessEvents
|
||||
Diff between previous and next state:
|
||||
checkpoint.numberOfSuspends:
|
||||
0
|
||||
1
|
||||
checkpoint.flowState:
|
||||
Unstarted(flowStart=Explicit, frozenFlowLogic=74BA62EC5821EBD4FC4CBE129843F9ED6509DB37E6E3C8F85E3F7A8D84083500)
|
||||
Started(flowIORequest=ExecuteAsyncOperation(operation=net.corda.docs.tutorial.flowstatemachines.SummingOperationThrowing@40f4c23d), frozenFiber=15EC69204562BB396846768169AD4A339569D97AE841D805C230C513A8BA5BDE)
|
||||
isFlowResumed:
|
||||
true
|
||||
false
|
||||
|
||||
|
||||
--- Transition of flow [03ab886e-3fd3-4667-b944-ab6a3b1f90a7] ---
|
||||
Timestamp: 2018-06-01T17:38:52.549Z
|
||||
Event: DoRemainingWork
|
||||
Actions:
|
||||
ExecuteAsyncOperation(operation=net.corda.docs.tutorial.flowstatemachines.SummingOperationThrowing@40f4c23d)
|
||||
Continuation: ProcessEvents
|
||||
Diff between previous and intended state:
|
||||
null
|
||||
Diff between previous and next state:
|
||||
checkpoint.errorState:
|
||||
Clean
|
||||
Errored(errors=[FlowError(errorId=-8704604242619505379, exception=java.lang.IllegalStateException: You shouldn't be calling me)], propagatedIndex=0, propagating=false)
|
||||
|
||||
|
||||
--- Transition of flow [03ab886e-3fd3-4667-b944-ab6a3b1f90a7] ---
|
||||
Timestamp: 2018-06-01T17:38:52.555Z
|
||||
Event: DoRemainingWork
|
||||
Actions:
|
||||
|
||||
Continuation: ProcessEvents
|
||||
Diff between previous and next state:
|
||||
null
|
||||
|
||||
--- Transition of flow [03ab886e-3fd3-4667-b944-ab6a3b1f90a7] ---
|
||||
Timestamp: 2018-06-01T17:38:52.556Z
|
||||
Event: StartErrorPropagation
|
||||
Actions:
|
||||
ScheduleEvent(event=DoRemainingWork)
|
||||
Continuation: ProcessEvents
|
||||
Diff between previous and next state:
|
||||
checkpoint.errorState.propagating:
|
||||
false
|
||||
true
|
||||
|
||||
|
||||
--- Transition of flow [03ab886e-3fd3-4667-b944-ab6a3b1f90a7] ---
|
||||
Timestamp: 2018-06-01T17:38:52.606Z
|
||||
Event: DoRemainingWork
|
||||
Actions:
|
||||
PropagateErrors(errorMessages=[ErrorSessionMessage(flowException=null, errorId=-8704604242619505379)], sessions=[], senderUUID=861f07d6-4b8f-42bd-9b52-5152812db2ba)
|
||||
CreateTransaction
|
||||
RemoveCheckpoint(id=[03ab886e-3fd3-4667-b944-ab6a3b1f90a7])
|
||||
PersistDeduplicationFacts(deduplicationHandlers=[])
|
||||
ReleaseSoftLocks(uuid=03ab886e-3fd3-4667-b944-ab6a3b1f90a7)
|
||||
CommitTransaction
|
||||
AcknowledgeMessages(deduplicationHandlers=[])
|
||||
RemoveSessionBindings(sessionIds=[])
|
||||
RemoveFlow(flowId=[03ab886e-3fd3-4667-b944-ab6a3b1f90a7], removalReason=ErrorFinish(flowErrors=[FlowError(errorId=-8704604242619505379, exception=java.lang.IllegalStateException: You shouldn't be calling me)]), lastState=StateMachineState(checkpoint=Checkpoint(invocationContext=InvocationContext(origin=RPC(actor=Actor(id=Id(value=aliceUser), serviceId=AuthServiceId(value=NODE_CONFIG), owningLegalIdentity=O=Alice Corp, L=Madrid, C=ES)), trace=Trace(invocationId=26bcf0c3-f1d8-4098-a52d-3780f4095b7a, timestamp: 2018-06-01T17:38:52.234Z, entityType: Invocation, sessionId=393d1175-3bb1-4eb1-bff0-6ba317851260, timestamp: 2018-06-01T17:38:52.169Z, entityType: Session), actor=Actor(id=Id(value=aliceUser), serviceId=AuthServiceId(value=NODE_CONFIG), owningLegalIdentity=O=Alice Corp, L=Madrid, C=ES), externalTrace=null, impersonatedActor=null), ourIdentity=O=Alice Corp, L=Madrid, C=ES, sessions={}, subFlowStack=[Inlined(flowClass=class net.corda.docs.tutorial.flowstatemachines.ExampleSummingFlow, subFlowVersion=CorDappFlow(platformVersion=1, corDappName=net.corda.docs-c6816652-f975-4fb2-aa09-ef1dddea19b3, corDappHash=F4012397D8CF97926B5998E046DBCE16D497318BB87DCED66313912D4B303BB7))], flowState=Started(flowIORequest=ExecuteAsyncOperation(operation=net.corda.docs.tutorial.flowstatemachines.SummingOperationThrowing@40f4c23d), frozenFiber=15EC69204562BB396846768169AD4A339569D97AE841D805C230C513A8BA5BDE), errorState=Errored(errors=[FlowError(errorId=-8704604242619505379, exception=java.lang.IllegalStateException: You shouldn't be calling me)], propagatedIndex=1, propagating=true), numberOfSuspends=1, deduplicationSeed=03ab886e-3fd3-4667-b944-ab6a3b1f90a7), flowLogic=net.corda.docs.tutorial.flowstatemachines.ExampleSummingFlow@600b0c6c, pendingDeduplicationHandlers=[], isFlowResumed=false, isTransactionTracked=false, isAnyCheckpointPersisted=true, isStartIdempotent=false, isRemoved=true, senderUUID=861f07d6-4b8f-42bd-9b52-5152812db2ba))
|
||||
Continuation: Abort
|
||||
Diff between previous and next state:
|
||||
checkpoint.errorState.propagatedIndex:
|
||||
0
|
||||
1
|
||||
isRemoved:
|
||||
false
|
||||
true
|
||||
|
||||
Whoa that's a lot of stuff. Now we get a glimpse into the bowels of the flow state machine. As we can see the flow did
|
||||
quite a few things, even though the flow code looks simple.
|
||||
|
||||
What we can see here is the different transitions the flow's state machine went through that led up to the error
|
||||
condition. For each transition we see what *Event* triggered the transition, what *Action* s were taken as a consequence,
|
||||
and how the internal *State* of the state machine was modified in the process. It also prints the transition's
|
||||
*Continuation*, which indicates how the flow should proceed after the transition.
|
||||
|
||||
For example in the first transition we can see that the triggering event was a ``DoRemainingWork``, this is a generic
|
||||
event that instructs the state machine to check its own state to see whether there's any work left to do, and does it if
|
||||
there's any.
|
||||
|
||||
In this case the work involves persisting a checkpoint together with some deduplication data in a database transaction,
|
||||
then acknowledging any triggering messages, signalling that the flow has started, and creating a fresh database
|
||||
transaction, to be used by user code.
|
||||
|
||||
The continuation is a ``Resume``, which instructs the state machine to hand control to user code. The state change is
|
||||
a simple update of bookkeeping data.
|
||||
|
||||
In other words the first transition concerns the initialization of the flow, which includes the creation of the
|
||||
checkpoint.
|
||||
|
||||
The next transition is the suspension of our summing operation, triggered by the ``Suspend`` event. As we can see in
|
||||
this transition we aren't doing any work related to the summation yet, we're merely persisting the checkpoint that
|
||||
indicates that we want to do the summation. Had we added a ``toString`` method to our ``SummingOperationThrowing`` we
|
||||
would see a nicer message.
|
||||
|
||||
The next transition is the faulty one, as we can see it was also triggered by a ``DoRemainingWork``, and executed our
|
||||
operation. We can see that there are two state "diff"s printed, one that would've happened had the transition succeeded,
|
||||
and one that actually happened, which marked the flow's state as errored. The rest of the transitions involve error
|
||||
propagation (triggered by the ``FlowHospital``) and notification of failure, which ultimately raises the exception on
|
||||
the RPC ``resultFuture``.
|
@ -13,4 +13,5 @@ Networks
|
||||
azure-vm-explore
|
||||
aws-vm-explore
|
||||
gcp-vm
|
||||
deploy-locally
|
||||
cipher-suites
|
||||
|
157
docs/source/deploy-locally.rst
Normal file
157
docs/source/deploy-locally.rst
Normal file
@ -0,0 +1,157 @@
|
||||
Setting up your local environment to allow the deployment of a Corda node
|
||||
=========================================================================
|
||||
|
||||
.. contents::
|
||||
|
||||
This document explains how to set up your local network to enable a
|
||||
Corda node to connect to the Corda Testnet. This assumes you are
|
||||
downloading a node ZIP from: https://testnet.corda.network.
|
||||
|
||||
|
||||
Pre-requisites
|
||||
--------------
|
||||
* Register for an account on https://testnet.corda.network.
|
||||
|
||||
|
||||
Set up your local network
|
||||
-------------------------
|
||||
|
||||
For a Corda node to be able to connect to the Corda Testnet and be
|
||||
reachable by counterparties on that network it needs to be reachable
|
||||
on the open internet. Corda is a server which requires an externally
|
||||
visible IP address and several ports in order to operate correctly.
|
||||
|
||||
We recommend running your Coda node on cloud infrastructure. If you
|
||||
wish to run Corda on your local machine then you will need to
|
||||
configure your network to enable the Corda node to be reachable from
|
||||
the internet.
|
||||
|
||||
.. note:: You will need access to your network router/gateway to the internet. If you do not have direct access then contact your administrator.
|
||||
|
||||
The following steps will describe how to use port forwarding on your
|
||||
router to make sure packets intended for Corda are routed to the right
|
||||
place on your local network.
|
||||
|
||||
Set up static IP address local host machine
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The next steps will configure your router to forward
|
||||
packets to the Corda node, but for this it is required to set the host
|
||||
machine to have a static IP address. If this isn't done, and the
|
||||
network is using DHCP dynamic address allocation then the next time
|
||||
the host machine is rebooted it may be on a different IP and the port
|
||||
forwarding will no longer work.
|
||||
|
||||
Please consult your operating system documentation for instructions on
|
||||
setting a static IP on the host machine.
|
||||
|
||||
|
||||
Set up port forwarding on your router
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Port forwarding is a method of making a computer on your network
|
||||
accessible to computers on the Internet, even though it is behind a router.
|
||||
|
||||
.. note:: All routers are slightly different and you will need to consult the documentation for your specific make and model.
|
||||
|
||||
Log in to the admin page of your router (often ``192.168.0.1``) in your
|
||||
browser bar.
|
||||
|
||||
.. note:: Router administration IP and log in credentials are usually on the bottom or side of your router.
|
||||
|
||||
Navigate to the ``port forwarding`` section of the admin console.
|
||||
|
||||
Add rules for the following ports which Corda uses:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
10002
|
||||
10003
|
||||
8080
|
||||
|
||||
.. note:: These ports are the defaults for Testnet which are specified
|
||||
in the node.conf. If these conflict with existing services
|
||||
on your host machine they can be changed in the
|
||||
``/opt/corda/node.conf`` file.
|
||||
|
||||
For each rule you will also typically have to specify the rule name,
|
||||
the static IP address of the host machine we configured earlier (the
|
||||
same in each case) and the protocol (which is TCP in all cases here).
|
||||
|
||||
Please consult your router documentation for specific details on
|
||||
enabling port forwarding.
|
||||
|
||||
|
||||
Open firewall ports
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are operating a firewall on your host machine or local network
|
||||
you will also need to open the above ports for incoming traffic.
|
||||
|
||||
Please consult your firewall documentation for details.
|
||||
|
||||
|
||||
Optional: Configure a static external IP address
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Corda expects nodes to have stable addresses over long periods of
|
||||
time. ISPs typically assign dynamic IP addresses to a router and so if
|
||||
your router is rebooted it may not obtain the same external IP and
|
||||
therefore your Corda node will change its address on the Testnet.
|
||||
|
||||
You can request a static IP address from your ISP however this may
|
||||
incur a cost.
|
||||
|
||||
If the IP address does change then this doesn't cause issues but it
|
||||
will result in an update to the network map which then needs to be
|
||||
propagated to all peers in the network. There may be some delay in the
|
||||
ability to transact while this happens.
|
||||
|
||||
.. warning:: Corda nodes are expected to be online all the time and
|
||||
will send a heartbeat to the network map server to
|
||||
indicate they are operational. If they go offline for a
|
||||
period of time (~24 hours in the case of Testnet) then
|
||||
the node will be removed from the network map. Any nodes
|
||||
which have queued messages for your node will drop these messages,
|
||||
they won't be delivered and unexpected behaviour may
|
||||
occur.
|
||||
|
||||
|
||||
Test if the ports are open
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can use a port checking tool to make sure the ports are open
|
||||
properly.
|
||||
|
||||
|
||||
Download and install your node
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Navigate to https://testnet.corda.network/platform.
|
||||
|
||||
Click on the ``Download`` button and wait for the ZIP
|
||||
file to download:
|
||||
|
||||
.. image:: resources/testnet-download.png
|
||||
|
||||
.. note: This may take several seconds.
|
||||
|
||||
Unzip the file in your Corda root directory:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
mkdir corda
|
||||
cd corda
|
||||
cp <PATH_TO_DOWNLOAD>/node.zip .
|
||||
unzip node.zip
|
||||
cd node
|
||||
|
||||
Run the ``run-corda.sh`` script to start your Corda node.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
./run-corda.sh
|
||||
|
||||
Congratulations! You now have a running Corda node on Testnet.
|
||||
|
||||
.. warning:: It is possible to copy the ``node.zip`` file from your local machine to any other host machine and run the Corda node from there. Do not run multiple copies of the same node (i.e. with the same identity). If a new copy of the node appears on the network then the network map server will interpret this as a change in the address of the node and route traffic to the most recent instance. Any states which are on the old node will no longer be available and undefined behaviour may result. Please provision a new node from the application instead.
|
@ -34,6 +34,11 @@ sourceSets {
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/kotlin')
|
||||
}
|
||||
java {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/java')
|
||||
}
|
||||
resources {
|
||||
srcDir file('../../testing/test-utils/src/main/resources')
|
||||
}
|
||||
|
@ -0,0 +1,124 @@
|
||||
package net.corda.docs;
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient;
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.core.contracts.Amount;
|
||||
import net.corda.core.contracts.Issued;
|
||||
import net.corda.core.contracts.Structures;
|
||||
import net.corda.core.messaging.CordaRPCOps;
|
||||
import net.corda.core.node.services.Vault;
|
||||
import net.corda.core.utilities.OpaqueBytes;
|
||||
import net.corda.finance.contracts.asset.Cash;
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow;
|
||||
import net.corda.finance.flows.CashPaymentFlow;
|
||||
import net.corda.testing.driver.DriverParameters;
|
||||
import net.corda.testing.driver.NodeHandle;
|
||||
import net.corda.testing.driver.NodeParameters;
|
||||
import net.corda.testing.node.User;
|
||||
import org.junit.Test;
|
||||
import rx.Observable;
|
||||
|
||||
import java.util.Currency;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static net.corda.finance.Currencies.DOLLARS;
|
||||
import static net.corda.node.services.Permissions.invokeRpc;
|
||||
import static net.corda.node.services.Permissions.startFlow;
|
||||
import static net.corda.testing.core.ExpectKt.expect;
|
||||
import static net.corda.testing.core.ExpectKt.expectEvents;
|
||||
import static net.corda.testing.core.TestConstants.ALICE_NAME;
|
||||
import static net.corda.testing.core.TestConstants.BOB_NAME;
|
||||
import static net.corda.testing.driver.Driver.driver;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class JavaIntegrationTestingTutorial {
|
||||
@Test
|
||||
public void aliceBobCashExchangeExample() {
|
||||
// START 1
|
||||
driver(new DriverParameters()
|
||||
.withStartNodesInProcess(true)
|
||||
.withExtraCordappPackagesToScan(singletonList("net.corda.finance.contracts.asset")), dsl -> {
|
||||
|
||||
User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList(
|
||||
startFlow(CashIssueAndPaymentFlow.class),
|
||||
invokeRpc("vaultTrack")
|
||||
)));
|
||||
|
||||
User bobUser = new User("bobUser", "testPassword2", new HashSet<>(asList(
|
||||
startFlow(CashPaymentFlow.class),
|
||||
invokeRpc("vaultTrack")
|
||||
)));
|
||||
|
||||
try {
|
||||
List<CordaFuture<NodeHandle>> nodeHandleFutures = asList(
|
||||
dsl.startNode(new NodeParameters().withProvidedName(ALICE_NAME).withRpcUsers(singletonList(aliceUser))),
|
||||
dsl.startNode(new NodeParameters().withProvidedName(BOB_NAME).withRpcUsers(singletonList(bobUser)))
|
||||
);
|
||||
|
||||
NodeHandle alice = nodeHandleFutures.get(0).get();
|
||||
NodeHandle bob = nodeHandleFutures.get(1).get();
|
||||
// END 1
|
||||
|
||||
// START 2
|
||||
CordaRPCClient aliceClient = new CordaRPCClient(alice.getRpcAddress());
|
||||
CordaRPCOps aliceProxy = aliceClient.start("aliceUser", "testPassword1").getProxy();
|
||||
|
||||
CordaRPCClient bobClient = new CordaRPCClient(bob.getRpcAddress());
|
||||
CordaRPCOps bobProxy = bobClient.start("bobUser", "testPassword2").getProxy();
|
||||
// END 2
|
||||
|
||||
// START 3
|
||||
Observable<Vault.Update<Cash.State>> bobVaultUpdates = bobProxy.vaultTrack(Cash.State.class).getUpdates();
|
||||
Observable<Vault.Update<Cash.State>> aliceVaultUpdates = aliceProxy.vaultTrack(Cash.State.class).getUpdates();
|
||||
// END 3
|
||||
|
||||
// START 4
|
||||
OpaqueBytes issueRef = OpaqueBytes.of((byte)0);
|
||||
aliceProxy.startFlowDynamic(
|
||||
CashIssueAndPaymentFlow.class,
|
||||
DOLLARS(1000),
|
||||
issueRef,
|
||||
bob.getNodeInfo().getLegalIdentities().get(0),
|
||||
true,
|
||||
dsl.getDefaultNotaryIdentity()
|
||||
).getReturnValue().get();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<Vault.Update<Cash.State>> cashVaultUpdateClass = (Class<Vault.Update<Cash.State>>)(Class<?>)Vault.Update.class;
|
||||
|
||||
expectEvents(bobVaultUpdates, true, () ->
|
||||
expect(cashVaultUpdateClass, update -> true, update -> {
|
||||
System.out.println("Bob got vault update of " + update);
|
||||
Amount<Issued<Currency>> amount = update.getProduced().iterator().next().getState().getData().getAmount();
|
||||
assertEquals(DOLLARS(1000), Structures.withoutIssuer(amount));
|
||||
return null;
|
||||
})
|
||||
);
|
||||
// END 4
|
||||
|
||||
// START 5
|
||||
bobProxy.startFlowDynamic(
|
||||
CashPaymentFlow.class,
|
||||
DOLLARS(1000),
|
||||
alice.getNodeInfo().getLegalIdentities().get(0)
|
||||
).getReturnValue().get();
|
||||
|
||||
expectEvents(aliceVaultUpdates, true, () ->
|
||||
expect(cashVaultUpdateClass, update -> true, update -> {
|
||||
System.out.println("Alice got vault update of " + update);
|
||||
Amount<Issued<Currency>> amount = update.getProduced().iterator().next().getState().getData().getAmount();
|
||||
assertEquals(DOLLARS(1000), Structures.withoutIssuer(amount));
|
||||
return null;
|
||||
})
|
||||
);
|
||||
// END 5
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Exception thrown in driver DSL", e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package net.corda.docs.java;
|
||||
|
||||
import kotlin.Unit;
|
||||
import net.corda.client.rpc.CordaRPCClient;
|
||||
import net.corda.core.messaging.CordaRPCOps;
|
||||
import net.corda.core.utilities.KotlinUtilsKt;
|
||||
import net.corda.docs.java.tutorial.flowstatemachines.ExampleSummingFlow;
|
||||
import net.corda.node.services.Permissions;
|
||||
import net.corda.testing.driver.*;
|
||||
import net.corda.testing.node.User;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static net.corda.testing.core.TestConstants.ALICE_NAME;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public final class TutorialFlowAsyncOperationTest {
|
||||
// DOCSTART summingWorks
|
||||
@Test
|
||||
public final void summingWorks() {
|
||||
Driver.driver(new DriverParameters(), (DriverDSL dsl) -> {
|
||||
User aliceUser = new User("aliceUser", "testPassword1",
|
||||
new HashSet<>(Collections.singletonList(Permissions.all()))
|
||||
);
|
||||
Future<NodeHandle> aliceFuture = dsl.startNode(new NodeParameters()
|
||||
.withProvidedName(ALICE_NAME)
|
||||
.withRpcUsers(Collections.singletonList(aliceUser))
|
||||
);
|
||||
NodeHandle alice = KotlinUtilsKt.getOrThrow(aliceFuture, null);
|
||||
CordaRPCClient aliceClient = new CordaRPCClient(alice.getRpcAddress());
|
||||
CordaRPCOps aliceProxy = aliceClient.start("aliceUser", "testPassword1").getProxy();
|
||||
Future<Integer> answerFuture = aliceProxy.startFlowDynamic(ExampleSummingFlow.class).getReturnValue();
|
||||
int answer = KotlinUtilsKt.getOrThrow(answerFuture, null);
|
||||
assertEquals(3, answer);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND summingWorks
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.withoutIssuer
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.messaging.vaultTrackBy
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.node.User
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class KotlinIntegrationTestingTutorial {
|
||||
@Test
|
||||
fun `alice bob cash exchange example`() {
|
||||
// START 1
|
||||
driver(DriverParameters(
|
||||
startNodesInProcess = true,
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")
|
||||
)) {
|
||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
||||
startFlow<CashIssueAndPaymentFlow>(),
|
||||
invokeRpc("vaultTrackBy")
|
||||
))
|
||||
|
||||
val bobUser = User("bobUser", "testPassword2", permissions = setOf(
|
||||
startFlow<CashPaymentFlow>(),
|
||||
invokeRpc("vaultTrackBy")
|
||||
))
|
||||
|
||||
val (alice, bob) = listOf(
|
||||
startNode(providedName = ALICE_NAME, rpcUsers = listOf(aliceUser)),
|
||||
startNode(providedName = BOB_NAME, rpcUsers = listOf(bobUser))
|
||||
).map { it.getOrThrow() }
|
||||
// END 1
|
||||
|
||||
// START 2
|
||||
val aliceClient = CordaRPCClient(alice.rpcAddress)
|
||||
val aliceProxy: CordaRPCOps = aliceClient.start("aliceUser", "testPassword1").proxy
|
||||
|
||||
val bobClient = CordaRPCClient(bob.rpcAddress)
|
||||
val bobProxy: CordaRPCOps = bobClient.start("bobUser", "testPassword2").proxy
|
||||
// END 2
|
||||
|
||||
// START 3
|
||||
val bobVaultUpdates: Observable<Vault.Update<Cash.State>> = bobProxy.vaultTrackBy<Cash.State>().updates
|
||||
val aliceVaultUpdates: Observable<Vault.Update<Cash.State>> = aliceProxy.vaultTrackBy<Cash.State>().updates
|
||||
// END 3
|
||||
|
||||
// START 4
|
||||
val issueRef = OpaqueBytes.of(0)
|
||||
aliceProxy.startFlow(::CashIssueAndPaymentFlow,
|
||||
1000.DOLLARS,
|
||||
issueRef,
|
||||
bob.nodeInfo.singleIdentity(),
|
||||
true,
|
||||
defaultNotaryIdentity
|
||||
).returnValue.getOrThrow()
|
||||
|
||||
bobVaultUpdates.expectEvents {
|
||||
expect { update ->
|
||||
println("Bob got vault update of $update")
|
||||
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
|
||||
assertEquals(1000.DOLLARS, amount.withoutIssuer())
|
||||
}
|
||||
}
|
||||
// END 4
|
||||
|
||||
// START 5
|
||||
bobProxy.startFlow(::CashPaymentFlow, 1000.DOLLARS, alice.nodeInfo.singleIdentity()).returnValue.getOrThrow()
|
||||
|
||||
aliceVaultUpdates.expectEvents {
|
||||
expect { update ->
|
||||
println("Alice got vault update of $update")
|
||||
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
|
||||
assertEquals(1000.DOLLARS, amount.withoutIssuer())
|
||||
}
|
||||
}
|
||||
// END 5
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.docs.tutorial.flowstatemachines.ExampleSummingFlow
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.node.User
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TutorialFlowAsyncOperationTest {
|
||||
// DOCSTART summingWorks
|
||||
@Test
|
||||
fun summingWorks() {
|
||||
driver(DriverParameters(startNodesInProcess = true)) {
|
||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(Permissions.all()))
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(aliceUser)).getOrThrow()
|
||||
val aliceClient = CordaRPCClient(alice.rpcAddress)
|
||||
val aliceProxy = aliceClient.start("aliceUser", "testPassword1").proxy
|
||||
val answer = aliceProxy.startFlow(::ExampleSummingFlow).returnValue.getOrThrow()
|
||||
assertEquals(3, answer)
|
||||
}
|
||||
}
|
||||
// DOCEND summingWorks
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package net.corda.docs.java.tutorial.flowstatemachines;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import net.corda.core.flows.FlowLogic;
|
||||
import net.corda.core.flows.StartableByRPC;
|
||||
import net.corda.core.internal.FlowAsyncOperationKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
// DOCSTART ExampleSummingFlow
|
||||
@StartableByRPC
|
||||
public final class ExampleSummingFlow extends FlowLogic<Integer> {
|
||||
@Suspendable
|
||||
@NotNull
|
||||
@Override
|
||||
public Integer call() {
|
||||
return FlowAsyncOperationKt.executeAsync(this, new SummingOperation(1, 2), false);
|
||||
}
|
||||
}
|
||||
// DOCEND ExampleSummingFlow
|
@ -0,0 +1,32 @@
|
||||
package net.corda.docs.java.tutorial.flowstatemachines;
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.core.internal.FlowAsyncOperation;
|
||||
import net.corda.core.internal.concurrent.CordaFutureImplKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
// DOCSTART SummingOperation
|
||||
public final class SummingOperation implements FlowAsyncOperation<Integer> {
|
||||
private final int a;
|
||||
private final int b;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CordaFuture<Integer> execute() {
|
||||
return CordaFutureImplKt.doneFuture(this.a + this.b);
|
||||
}
|
||||
|
||||
public final int getA() {
|
||||
return this.a;
|
||||
}
|
||||
|
||||
public final int getB() {
|
||||
return this.b;
|
||||
}
|
||||
|
||||
public SummingOperation(int a, int b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
}
|
||||
// DOCEND SummingOperation
|
@ -0,0 +1,31 @@
|
||||
package net.corda.docs.java.tutorial.flowstatemachines;
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.core.internal.FlowAsyncOperation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
// DOCSTART SummingOperationThrowing
|
||||
public final class SummingOperationThrowing implements FlowAsyncOperation<Integer> {
|
||||
private final int a;
|
||||
private final int b;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CordaFuture<Integer> execute() {
|
||||
throw new IllegalStateException("You shouldn't be calling me");
|
||||
}
|
||||
|
||||
public final int getA() {
|
||||
return this.a;
|
||||
}
|
||||
|
||||
public final int getB() {
|
||||
return this.b;
|
||||
}
|
||||
|
||||
public SummingOperationThrowing(int a, int b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
}
|
||||
// DOCEND SummingOperationThrowing
|
@ -0,0 +1,38 @@
|
||||
package net.corda.docs.tutorial.flowstatemachines
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.internal.FlowAsyncOperation
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.executeAsync
|
||||
|
||||
// DOCSTART SummingOperation
|
||||
class SummingOperation(val a: Int, val b: Int) : FlowAsyncOperation<Int> {
|
||||
override fun execute(): CordaFuture<Int> {
|
||||
return doneFuture(a + b)
|
||||
}
|
||||
}
|
||||
// DOCEND SummingOperation
|
||||
|
||||
// DOCSTART SummingOperationThrowing
|
||||
class SummingOperationThrowing(val a: Int, val b: Int) : FlowAsyncOperation<Int> {
|
||||
override fun execute(): CordaFuture<Int> {
|
||||
throw IllegalStateException("You shouldn't be calling me")
|
||||
}
|
||||
}
|
||||
// DOCEND SummingOperationThrowing
|
||||
|
||||
// DOCSTART ExampleSummingFlow
|
||||
@StartableByRPC
|
||||
class ExampleSummingFlow : FlowLogic<Int>() {
|
||||
@Suspendable
|
||||
override fun call(): Int {
|
||||
val answer = executeAsync(SummingOperation(1, 2))
|
||||
return answer // hopefully 3
|
||||
}
|
||||
}
|
||||
// DOCEND ExampleSummingFlow
|
||||
|
@ -115,9 +115,11 @@ As well as input states and output states, transactions contain:
|
||||
* Commands
|
||||
* Attachments
|
||||
* Time-Window
|
||||
* Notary
|
||||
|
||||
For example, a transaction where Alice pays off £5 of an IOU with Bob using a £5 cash payment, supported by two
|
||||
attachments and a time-window, may look as follows:
|
||||
For example, suppose we have a transaction where Alice uses a £5 cash payment to pay off £5 of an IOU with Bob.
|
||||
This transaction has two supporting attachments and will only be notarised by NotaryClusterA if the notary pool
|
||||
receives it within the specified time-window. This transaction would look as follows:
|
||||
|
||||
.. image:: resources/full-tx.png
|
||||
:scale: 25%
|
||||
@ -184,3 +186,13 @@ In some cases, we want a transaction proposed to only be approved during a certa
|
||||
|
||||
In such cases, we can add a *time-window* to the transaction. Time-windows specify the time window during which the
|
||||
transaction can be committed. We discuss time-windows in the section on :doc:`key-concepts-time-windows`.
|
||||
|
||||
Notary
|
||||
^^^^^^
|
||||
A notary pool is a network service that provides uniqueness consensus by attesting that, for a given transaction,
|
||||
it has not already signed other transactions that consume any of the proposed transaction’s input states.
|
||||
The notary pool provides the point of finality in the system.
|
||||
|
||||
Note that if the notary entity is absent then the transaction is not notarised at all. This is intended for
|
||||
issuance/genesis transactions that don't consume any other states and thus can't double spend anything.
|
||||
For more information on the notary services, see :doc:`key-concepts-notaries`.
|
@ -31,10 +31,10 @@ regular time interval for network map and applies any related changes locally.
|
||||
Nodes do not automatically deregister themselves, so (for example) nodes going offline briefly for maintenance are retained
|
||||
in the network map, and messages for them will be queued, minimising disruption.
|
||||
|
||||
Additionally, on every restart and on daily basis nodes submit signed `NodeInfo`s to the map service. When network map gets
|
||||
signed, these changes are distributed as new network data. `NodeInfo` republishing is treated as a heartbeat from the node,
|
||||
Additionally, on every restart and on daily basis nodes submit signed ``NodeInfo`` s to the map service. When network map gets
|
||||
signed, these changes are distributed as new network data. ``NodeInfo`` republishing is treated as a heartbeat from the node,
|
||||
based on that network map service is able to figure out which nodes can be considered as stale and removed from the network
|
||||
map document after `eventHorizon` time.
|
||||
map document after ``eventHorizon`` time.
|
||||
|
||||
Message queues
|
||||
--------------
|
||||
@ -61,10 +61,10 @@ for maintenance and other minor purposes.
|
||||
corresponding bridge is used to forward the message to an advertising peer's p2p queue. Once a peer is picked the
|
||||
session continues on as normal.
|
||||
|
||||
:``rpc.requests``:
|
||||
:``rpc.server``:
|
||||
RPC clients send their requests here, and it's only open for sending by clients authenticated as RPC users.
|
||||
|
||||
:``clients.$user.rpc.$random``:
|
||||
:``rpc.client.$user.$random``:
|
||||
RPC clients are given permission to create a temporary queue incorporating their username (``$user``) and sole
|
||||
permission to receive messages from it. RPC requests are required to include a random number (``$random``) from
|
||||
which the node is able to construct the queue the user is listening on and send the response to that. This mechanism
|
||||
@ -80,7 +80,7 @@ Clients attempting to connect to the node's broker fall in one of four groups:
|
||||
they are given full access to all valid queues, otherwise they are rejected.
|
||||
|
||||
#. Anyone connecting with the username ``SystemUsers/Peer`` is treated as a peer on the same Corda network as the node. Their
|
||||
TLS root CA must be the same as the node's root CA - the root CA is the doorman of the network and having the same root CA
|
||||
TLS root CA must be the same as the node's root CA -- the root CA is the doorman of the network and having the same root CA
|
||||
implies we've been let in by the same doorman. If they are part of the same network then they are only given permission
|
||||
to send to our ``p2p.inbound.$identity`` queue, otherwise they are rejected.
|
||||
|
||||
@ -98,20 +98,19 @@ this to determine what permissions the user has.
|
||||
The broker also does host verification when connecting to another peer. It checks that the TLS certificate subject matches
|
||||
with the advertised X.500 legal name from the network map service.
|
||||
|
||||
|
||||
Implementation details
|
||||
----------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The components of the system that need to communicate and authenticate each other are:
|
||||
- The Artemis P2P broker (Currently runs inside the Nodes JVM process, but in the future it will be able to run as a separate server)
|
||||
* opens Acceptor configured with the doorman's certificate in the truststore and the node's ssl certificate in the keystore
|
||||
- The Artemis RPC broker (Currently runs inside the Nodes JVM process, but in the future it will be able to run as a separate server)
|
||||
* opens "Admin" Acceptor configured with the doorman's certificate in the truststore and the node's ssl certificate in the keystore
|
||||
* opens "Client" Acceptor with the ssl settings configurable. This acceptor does not require ssl client-auth.
|
||||
- The current node hosting the brokers
|
||||
* connects to the P2P broker using the ``SystemUsers/Node`` user and the node's keystore and trustore
|
||||
* connects to the "Admin" Acceptor of the RPC broker using the ``SystemUsers/NodeRPC`` user and the node's keystore and trustore
|
||||
- RPC clients ( Third party applications that need to communicate with the Node. )
|
||||
* connect to the "Client" Acceptor of the RPC broker using the username/password provided by the node's admin. The client verifies the node's certificate using a truststore provided by the node's admin.
|
||||
- Peer nodes (Other nodes on the network)
|
||||
* connect to the P2P broker using the ``SystemUsers/Peer`` user and a doorman signed certificate. The authentication is performed based on the root CA.
|
||||
- The Artemis P2P broker (currently runs inside the node's JVM process, but in the future it will be able to run as a separate server):
|
||||
* Opens Acceptor configured with the doorman's certificate in the trustStore and the node's SSL certificate in the keyStore.
|
||||
- The Artemis RPC broker (currently runs inside the node's JVM process, but in the future it will be able to run as a separate server):
|
||||
* Opens "Admin" Acceptor configured with the doorman's certificate in the trustStore and the node's SSL certificate in the keyStore.
|
||||
* Opens "Client" Acceptor with the SSL settings configurable. This acceptor does not require SSL client-auth.
|
||||
- The current node hosting the brokers:
|
||||
* Connects to the P2P broker using the ``SystemUsers/Node`` user and the node's keyStore and trustStore.
|
||||
* Connects to the "Admin" Acceptor of the RPC broker using the ``SystemUsers/NodeRPC`` user and the node's keyStore and trustStore.
|
||||
- RPC clients (third party applications that need to communicate with the node):
|
||||
* Connect to the "Client" Acceptor of the RPC broker using the username/password provided by the node's admin. The client verifies the node's certificate using a trustStore provided by the node's admin.
|
||||
- Peer nodes (other nodes on the network):
|
||||
* Connect to the P2P broker using the ``SystemUsers/Peer`` user and a doorman signed certificate. The authentication is performed based on the root CA.
|
@ -14,27 +14,15 @@ The node services represent the various sub functions of the Corda node.
|
||||
Some are directly accessible to contracts and flows through the
|
||||
``ServiceHub``, whilst others are the framework internals used to host
|
||||
the node functions. Any public service interfaces are defined in the
|
||||
``:core`` gradle project in the
|
||||
``src/main/kotlin/net/corda/core/node/services`` folder. The
|
||||
``ServiceHub`` interface exposes functionality suitable for flows.
|
||||
The implementation code for all standard services lives in the gradle
|
||||
``:node`` project under the ``src/main/kotlin/net/corda/node/services``
|
||||
folder. The ``src/main/kotlin/net/corda/node/services/api`` folder
|
||||
contains declarations for internal only services and for interoperation
|
||||
between services.
|
||||
``net.corda.core.node.services`` package. The ``ServiceHub`` interface exposes
|
||||
functionality suitable for flows.
|
||||
The implementation code for all standard services lives in the ``net.corda.node.services`` package.
|
||||
|
||||
All the services are constructed in the ``AbstractNode`` ``start``
|
||||
method (and the extension in ``Node``). They may also register a
|
||||
shutdown handler during initialisation, which will be called in reverse
|
||||
order to the start registration sequence when the ``Node.stop``
|
||||
is called.
|
||||
method. They may also register a shutdown handler during initialisation,
|
||||
which will be called in reverse order to the start registration sequence when the ``Node.stop`` is called.
|
||||
|
||||
For unit testing a number of non-persistent, memory only services are
|
||||
defined in the ``:node`` and ``:test-utils`` projects. The
|
||||
``:test-utils`` project also provides an in-memory networking simulation
|
||||
to allow unit testing of flows and service functions.
|
||||
|
||||
The roles of the individual services are described below.
|
||||
The roles of the individual services are described below.
|
||||
|
||||
Key management and identity services
|
||||
------------------------------------
|
||||
@ -43,15 +31,15 @@ InMemoryIdentityService
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``InMemoryIdentityService`` implements the ``IdentityService``
|
||||
interface and provides a store of remote mappings between ``CompositeKey``
|
||||
interface and provides a store of remote mappings between ``PublicKey``
|
||||
and remote ``Parties``. It is automatically populated from the
|
||||
``NetworkMapCache`` updates and is used when translating ``CompositeKey``
|
||||
``NetworkMapCache`` updates and is used when translating ``PublicKey``
|
||||
exposed in transactions into fully populated ``Party`` identities. This
|
||||
service is also used in the default JSON mapping of parties in the web
|
||||
server, thus allowing the party names to be used to refer to other nodes'
|
||||
legal identities. In the future the Identity service will be made
|
||||
persistent and extended to allow anonymised session keys to be used in
|
||||
flows where the well-known ``CompositeKey`` of nodes need to be hidden
|
||||
flows where the well-known ``PublicKey`` of nodes need to be hidden
|
||||
to non-involved parties.
|
||||
|
||||
PersistentKeyManagementService and E2ETestKeyManagementService
|
||||
@ -95,7 +83,7 @@ of this component, because the ``ArtemisMessagingServer`` is responsible
|
||||
for configuring the network ports (based upon settings in ``node.conf``)
|
||||
and the service configures the security settings of the ``ArtemisMQ``
|
||||
middleware and acts to form bridges between node mailbox queues based
|
||||
upon connection details advertised by the ``NetworkMapService``. The
|
||||
upon connection details advertised by the ``NetworkMapCache``. The
|
||||
``ArtemisMQ`` broker is configured to use TLS1.2 with a custom
|
||||
``TrustStore`` containing a Corda root certificate and a ``KeyStore``
|
||||
with a certificate and key signed by a chain back to this root
|
||||
@ -105,13 +93,13 @@ each other it is essential that the entire set of nodes are able to
|
||||
authenticate against each other and thus typically that they share a
|
||||
common root certificate. Also note that the address configuration
|
||||
defined for the server is the basis for the address advertised in the
|
||||
NetworkMapService and thus must be externally connectable by all nodes
|
||||
``NetworkMapCache`` and thus must be externally connectable by all nodes
|
||||
in the network.
|
||||
|
||||
NodeMessagingClient
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
P2PMessagingClient
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``NodeMessagingClient`` is the implementation of the
|
||||
The ``P2PMessagingClient`` is the implementation of the
|
||||
``MessagingService`` interface operating across the ``ArtemisMQ``
|
||||
middleware layer. It typically connects to the local ``ArtemisMQ``
|
||||
hosted within the ``ArtemisMessagingServer`` service. However, the
|
||||
@ -133,7 +121,7 @@ services of authorised nodes provided by the remote
|
||||
specific advertised services e.g. a Notary service, or an Oracle
|
||||
service. Also, this service allows mapping of friendly names, or
|
||||
``Party`` identities to the full ``NodeInfo`` which is used in the
|
||||
``StateMachineManager`` to convert between the ``CompositeKey``, or
|
||||
``StateMachineManager`` to convert between the ``PublicKey``, or
|
||||
``Party`` based addressing used in the flows/contracts and the
|
||||
physical host and port information required for the physical
|
||||
``ArtemisMQ`` messaging layer.
|
||||
@ -141,13 +129,6 @@ physical host and port information required for the physical
|
||||
Storage and persistence related services
|
||||
----------------------------------------
|
||||
|
||||
StorageServiceImpl
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``StorageServiceImpl`` service simply hold references to the various
|
||||
persistence related services and provides a single grouped interface on
|
||||
the ``ServiceHub``.
|
||||
|
||||
DBCheckpointStorage
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -247,40 +228,7 @@ a reference to the state that triggered the event. The flow can then
|
||||
begin whatever action is required. Note that the scheduled activity
|
||||
occurs in all nodes holding the state in their Vault, it may therefore
|
||||
be required for the flow to exit early if the current node is not
|
||||
the intended initiator.
|
||||
|
||||
Notary flow implementation services
|
||||
-----------------------------------
|
||||
|
||||
PersistentUniquenessProvider, InMemoryUniquenessProvider and RaftUniquenessProvider
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These variants of ``UniquenessProvider`` service are used by the notary
|
||||
flows to track consumed states and thus reject double-spend
|
||||
scenarios. The ``InMemoryUniquenessProvider`` is for unit testing only,
|
||||
the default being the ``PersistentUniquenessProvider`` which records the
|
||||
changes to the DB. When the Raft based notary is active the states are
|
||||
tracked by the whole cluster using a ``RaftUniquenessProvider``. Outside
|
||||
of the notary flows themselves this service should not be accessed
|
||||
by any CorDapp components.
|
||||
|
||||
NotaryService (SimpleNotaryService, ValidatingNotaryService, RaftValidatingNotaryService)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``NotaryService`` is an abstract base class for the various concrete
|
||||
implementations of the Notary server flow. By default, a node does
|
||||
not run any ``NotaryService`` server component. For that you need to specify the ``notary`` config.
|
||||
The node may then participate in controlling state uniqueness when contacted by nodes
|
||||
using the ``NotaryFlow.Client`` ``subFlow``. The
|
||||
``SimpleNotaryService`` only offers protection against double spend, but
|
||||
does no further verification. The ``ValidatingNotaryService`` checks
|
||||
that proposed transactions are correctly signed by all keys listed in
|
||||
the commands and runs the contract verify to ensure that the rules of
|
||||
the state transition are being followed. The
|
||||
``RaftValidatingNotaryService`` further extends the flow to operate
|
||||
against a cluster of nodes running shared consensus state across the
|
||||
RAFT protocol (note this requires the additional configuration of the
|
||||
``notaryClusterAddresses`` property).
|
||||
the intended initiator.
|
||||
|
||||
Vault related services
|
||||
----------------------
|
||||
|
@ -71,22 +71,18 @@ The name must also obey the following constraints:
|
||||
|
||||
* The ``country`` attribute is a valid ISO 3166-1 two letter code in upper-case
|
||||
|
||||
* All attributes must obey the following constraints:
|
||||
|
||||
* Upper-case first letter
|
||||
* The ``organisation`` field of the name obeys the following constraints:
|
||||
* Has at least two letters
|
||||
* No leading or trailing whitespace
|
||||
* Does not include the following characters: ``,`` , ``=`` , ``$`` , ``"`` , ``'`` , ``\``
|
||||
* Does not include the following characters: ``,`` , ``"``, ``\``
|
||||
* Is in NFKC normalization form
|
||||
* Does not contain the null character
|
||||
* Only the latin, common and inherited unicode scripts are supported
|
||||
|
||||
* The ``organisation`` field of the name also obeys the following constraints:
|
||||
|
||||
* No double-spacing
|
||||
|
||||
* This is to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and
|
||||
character confusability attacks
|
||||
This is to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and
|
||||
character confusability attacks.
|
||||
|
||||
.. note:: The network operator of a Corda Network may put additional constraints on node naming in place.
|
||||
|
||||
External identifiers
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 217 KiB |
BIN
docs/source/resources/testnet-download.png
Normal file
BIN
docs/source/resources/testnet-download.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 237 KiB |
@ -1,119 +1,128 @@
|
||||
.. highlight:: kotlin
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||
|
||||
Integration testing
|
||||
===================
|
||||
|
||||
Integration testing involves bringing up nodes locally and testing
|
||||
invariants about them by starting flows and inspecting their state.
|
||||
Integration testing involves bringing up nodes locally and testing invariants about them by starting flows and inspecting
|
||||
their state.
|
||||
|
||||
In this tutorial we will bring up three nodes - Alice, Bob and a
|
||||
notary. Alice will issue cash to Bob, then Bob will send this cash
|
||||
back to Alice. We will see how to test some simple deterministic and
|
||||
nondeterministic invariants in the meantime.
|
||||
In this tutorial we will bring up three nodes - Alice, Bob and a notary. Alice will issue cash to Bob, then Bob will send
|
||||
this cash back to Alice. We will see how to test some simple deterministic and nondeterministic invariants in the meantime.
|
||||
|
||||
.. note:: This example where Alice is self-issuing cash is purely for
|
||||
demonstration purposes, in reality, cash would be issued by a bank
|
||||
and subsequently passed around.
|
||||
.. note:: This example where Alice is self-issuing cash is purely for demonstration purposes, in reality, cash would be
|
||||
issued by a bank and subsequently passed around.
|
||||
|
||||
In order to spawn nodes we will use the Driver DSL. This DSL allows
|
||||
one to start up node processes from code. It manages a network map
|
||||
service and safe shutting down of nodes in the background.
|
||||
In order to spawn nodes we will use the Driver DSL. This DSL allows one to start up node processes from code. It creates
|
||||
a local network where all the nodes see each other and provides safe shutting down of nodes in the background.
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 1
|
||||
:end-before: END 1
|
||||
:dedent: 8
|
||||
.. container:: codeset
|
||||
|
||||
The above code starts three nodes:
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 1
|
||||
:end-before: END 1
|
||||
:dedent: 8
|
||||
|
||||
* Alice, who has user permissions to start the ``CashIssueFlow`` and
|
||||
``CashPaymentFlow`` flows
|
||||
* Bob, who only has user permissions to start the ``CashPaymentFlow``
|
||||
* A notary that offers a ``ValidatingNotaryService``. We won't connect
|
||||
to the notary directly, so there's no need to provide a ``User``
|
||||
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||
:language: java
|
||||
:start-after: START 1
|
||||
:end-before: END 1
|
||||
:dedent: 8
|
||||
|
||||
The ``startNode`` function returns a future that completes once the
|
||||
node is fully started. This allows starting of the nodes to be
|
||||
parallel. We wait on these futures as we need the information
|
||||
returned; their respective ``NodeHandles`` s.
|
||||
The above code starts two nodes:
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 2
|
||||
:end-before: END 2
|
||||
:dedent: 12
|
||||
* Alice, configured with an RPC user who has permissions to start the ``CashIssueAndPaymentFlow`` flow on it and query
|
||||
Alice's vault.
|
||||
* Bob, configured with an RPC user who only has permissions to start the ``CashPaymentFlow`` and query Bob's vault.
|
||||
|
||||
After getting the handles we wait for both parties to register with
|
||||
the network map to ensure we don't have race conditions with network
|
||||
map registration. Next we connect to Alice and Bob respectively from
|
||||
the test process using the test user we created. Then we establish RPC
|
||||
links that allow us to start flows and query state.
|
||||
.. note:: You will notice that we did not start a notary. This is done automatically for us by the driver - it creates
|
||||
a notary node with the name ``DUMMY_NOTARY_NAME`` which is visible to both nodes. If you wish to customise this, for
|
||||
example create more notaries, then specify the ``DriverParameters.notarySpecs`` parameter.
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 3
|
||||
:end-before: END 3
|
||||
:dedent: 12
|
||||
The ``startNode`` function returns a ``CordaFuture`` object that completes once the node is fully started and visible on
|
||||
the local network. Returning a future allows starting of the nodes to be parallel. We wait on these futures as we need
|
||||
the information returned; their respective ``NodeHandles`` s.
|
||||
|
||||
We will be interested in changes to Alice's and Bob's vault, so we
|
||||
query a stream of vault updates from each.
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 2
|
||||
:end-before: END 2
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||
:language: java
|
||||
:start-after: START 2
|
||||
:end-before: END 2
|
||||
:dedent: 16
|
||||
|
||||
Next we connect to Alice and Bob from the test process using the test users we created. We establish RPC links that allow
|
||||
us to start flows and query state.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 3
|
||||
:end-before: END 3
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||
:language: java
|
||||
:start-after: START 3
|
||||
:end-before: END 3
|
||||
:dedent: 16
|
||||
|
||||
We will be interested in changes to Alice's and Bob's vault, so we query a stream of vault updates from each.
|
||||
|
||||
Now that we're all set up we can finally get some cash action going!
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 4
|
||||
:end-before: END 4
|
||||
:dedent: 12
|
||||
.. container:: codeset
|
||||
|
||||
The first loop creates 10 threads, each starting a ``CashFlow`` flow
|
||||
on the Alice node. We specify that we want to issue ``i`` dollars to
|
||||
Bob, setting our notary as the notary responsible for notarising the
|
||||
created states. Note that no notarisation will occur yet as we're not
|
||||
spending any states, only creating new ones on the ledger.
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 4
|
||||
:end-before: END 4
|
||||
:dedent: 12
|
||||
|
||||
We started the flows from different threads for the sake of the
|
||||
tutorial, to demonstrate how to test non-determinism, which is what
|
||||
the ``expectEvents`` block does.
|
||||
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||
:language: java
|
||||
:start-after: START 4
|
||||
:end-before: END 4
|
||||
:dedent: 16
|
||||
|
||||
The Expect DSL allows ordering constraints to be checked on a stream
|
||||
of events. The above code specifies that we are expecting 10 updates
|
||||
to be emitted on the ``bobVaultUpdates`` stream in unspecified order
|
||||
(this is what the ``parallel`` construct does). We specify an
|
||||
(otherwise optional) ``match`` predicate to identify specific updates
|
||||
we are interested in, which we then print.
|
||||
We start a ``CashIssueAndPaymentFlow`` flow on the Alice node. We specify that we want Alice to self-issue $1000 which is
|
||||
to be payed to Bob. We specify the default notary identity created by the driver as the notary responsible for notarising
|
||||
the created states. Note that no notarisation will occur yet as we're not spending any states, only creating new ones on
|
||||
the ledger.
|
||||
|
||||
If we run the code written so far we should see 4 nodes starting up
|
||||
(Alice, Bob, the notary and an implicit Network Map service), then
|
||||
10 logs of Bob receiving 1,2,...10 dollars from Alice in some unspecified
|
||||
order.
|
||||
We expect a single update to Bob's vault when it receives the $1000 from Alice. This is what the ``expectEvents`` call
|
||||
is asserting.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 5
|
||||
:end-before: END 5
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||
:language: java
|
||||
:start-after: START 5
|
||||
:end-before: END 5
|
||||
:dedent: 16
|
||||
|
||||
Next we want Bob to send this cash back to Alice.
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 5
|
||||
:end-before: END 5
|
||||
:dedent: 12
|
||||
That's it! We saw how to start up several corda nodes locally, how to connect to them, and how to test some simple invariants
|
||||
about ``CashIssueAndPaymentFlow`` and ``CashPaymentFlow``.
|
||||
|
||||
This time we'll do it sequentially. We make Bob pay 1,2,..10 dollars
|
||||
to Alice in order. We make sure that a the ``CashFlow`` has finished
|
||||
by waiting on ``startFlow`` 's ``returnValue``.
|
||||
|
||||
Then we use the Expect DSL again, this time using ``sequence`` to test
|
||||
for the updates arriving in the order we expect them to.
|
||||
|
||||
Note that ``parallel`` and ``sequence`` may be nested into each other
|
||||
arbitrarily to test more complex scenarios.
|
||||
|
||||
That's it! We saw how to start up several corda nodes locally, how to
|
||||
connect to them, and how to test some simple invariants about
|
||||
``CashFlow``.
|
||||
|
||||
To run the complete test you can open
|
||||
``example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt``
|
||||
from IntelliJ and run the test, or alternatively use gradle:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
# Run example-code integration tests
|
||||
./gradlew docs/source/example-code:integrationTest -i
|
||||
You can find the complete test at ``example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java``
|
||||
(Java) and ``example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt`` (Kotlin) in the
|
||||
`Corda repo <https://github.com/corda/corda>`_.
|
||||
|
@ -6,9 +6,9 @@ that can be easily queried and worked with.
|
||||
|
||||
The vault keeps track of both unconsumed and consumed states:
|
||||
|
||||
* Unconsumed (or unspent) states represent fungible states available for spending (including spend-to-self transactions)
|
||||
* **Unconsumed** (or unspent) states represent fungible states available for spending (including spend-to-self transactions)
|
||||
and linear states available for evolution (eg. in response to a lifecycle event on a deal) or transfer to another party.
|
||||
* Consumed (or spent) states represent ledger immutable state for the purpose of transaction reporting, audit and archival, including the ability to perform joins with app-private data (like customer notes)
|
||||
* **Consumed** (or spent) states represent ledger immutable state for the purpose of transaction reporting, audit and archival, including the ability to perform joins with app-private data (like customer notes).
|
||||
|
||||
By fungible we refer to assets of measurable quantity (eg. a cash currency, units of stock) which can be combined
|
||||
together to represent a single ledger state.
|
||||
@ -40,15 +40,15 @@ The following diagram illustrates the breakdown of the vault into sub-system com
|
||||
|
||||
Note the following:
|
||||
|
||||
* the vault "On Ledger" store tracks unconsumed state and is updated internally by the node upon recording of a transaction on the ledger
|
||||
(following successful smart contract verification and signature by all participants)
|
||||
* the vault "Off Ledger" store refers to additional data added by the node owner subsequent to transaction recording
|
||||
* the vault performs fungible state spending (and in future, fungible state optimisation management including merging, splitting and re-issuance)
|
||||
* vault extensions represent additional custom plugin code a developer may write to query specific custom contract state attributes.
|
||||
* customer "Off Ledger" (private store) represents internal organisational data that may be joined with the vault data to perform additional reporting or processing
|
||||
* a :doc:`Vault Query API </api-vault-query>` is exposed to developers using standard Corda RPC and CorDapp plugin mechanisms
|
||||
* a vault update API is internally used by transaction recording flows.
|
||||
* the vault database schemas are directly accessible via JDBC for customer joins and queries
|
||||
* The vault "On Ledger" store tracks unconsumed state and is updated internally by the node upon recording of a transaction on the ledger
|
||||
(following successful smart contract verification and signature by all participants).
|
||||
* The vault "Off Ledger" store refers to additional data added by the node owner subsequent to transaction recording.
|
||||
* The vault performs fungible state spending (and in future, fungible state optimisation management including merging, splitting and re-issuance).
|
||||
* Vault extensions represent additional custom plugin code a developer may write to query specific custom contract state attributes.
|
||||
* Customer "Off Ledger" (private store) represents internal organisational data that may be joined with the vault data to perform additional reporting or processing.
|
||||
* A :doc:`Vault Query API </api-vault-query>` is exposed to developers using standard Corda RPC and CorDapp plugin mechanisms.
|
||||
* A vault update API is internally used by transaction recording flows.
|
||||
* The vault database schemas are directly accessible via JDBC for customer joins and queries.
|
||||
|
||||
Section 8 of the `Technical white paper`_ describes features of the vault yet to be implemented including private key management, state splitting and merging, asset re-issuance and node event scheduling.
|
||||
|
||||
|
@ -94,26 +94,19 @@ shadowJar {
|
||||
// systems, the folder and file end up clashing, causing an error when trying to build the JAR.
|
||||
relocate 'META-INF/LICENSE', 'META-INF/LICENCE-2'
|
||||
dependencies {
|
||||
// Apache Curator 4.0.1 already contains a shaded copy of Guava 20.0
|
||||
include(dependency("org.apache.curator:curator-client:${curator_version}"))
|
||||
include(dependency("org.apache.curator:curator-recipes:${curator_version}"))
|
||||
include(dependency("org.apache.curator:curator-framework:${curator_version}"))
|
||||
include(dependency('org.apache.zookeeper:zookeeper:3.5.3-beta'))
|
||||
include(dependency('commons-cli:commons-cli:1.2'))
|
||||
include(dependency('io.netty:netty:3.10.5.Final'))
|
||||
include(dependency('com.google.guava:guava:20.0'))
|
||||
}
|
||||
relocate 'org.apache.curator.', 'net.corda.shaded.org.apache.curator.'
|
||||
relocate 'org.apache.zookeeper.', 'net.corda.shaded.org.apache.zookeeper.'
|
||||
relocate 'org.apache.jute.', 'net.corda.shaded.org.apache.jute.'
|
||||
relocate 'org.apache.commons.', 'net.corda.shaded.org.apache.commons.'
|
||||
relocate 'org.jboss.netty.', 'net.corda.shaded.org.jboss.netty.'
|
||||
relocate ('com.google.', 'net.corda.shaded.com.google.') {
|
||||
// This JAR uses annotations from these packages. However,
|
||||
// the annotation classes themselves are not included here
|
||||
// and so we cannot relocate their references.
|
||||
exclude 'com.google.errorprone.**'
|
||||
exclude 'com.google.j2objc.**'
|
||||
}
|
||||
}
|
||||
|
||||
task testJar(type: Jar) {
|
||||
|
@ -0,0 +1,73 @@
|
||||
package net.corda.serialization.reproduction;
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient;
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.core.flows.FlowLogic;
|
||||
import net.corda.core.flows.StartableByRPC;
|
||||
import net.corda.core.serialization.CordaSerializable;
|
||||
import net.corda.node.services.Permissions;
|
||||
import net.corda.testing.driver.Driver;
|
||||
import net.corda.testing.driver.DriverParameters;
|
||||
import net.corda.testing.driver.NodeHandle;
|
||||
import net.corda.testing.driver.NodeParameters;
|
||||
import net.corda.testing.node.User;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class GenericReturnFailureReproductionIntegrationTest {
|
||||
|
||||
@Test()
|
||||
public void flowShouldReturnGenericList() {
|
||||
User user = new User("yes", "yes", Collections.singleton(Permissions.startFlow(SuperSimpleGenericFlow.class)));
|
||||
DriverParameters defaultParameters = new DriverParameters();
|
||||
Driver.<Void>driver(defaultParameters, (driver) -> {
|
||||
NodeHandle startedNode = getOrThrow(driver.startNode(new NodeParameters().withRpcUsers(Collections.singletonList(user)).withStartInSameProcess(true)));
|
||||
(new CordaRPCClient(startedNode.getRpcAddress())).<Void>use("yes", "yes", (cordaRPCConnection -> {
|
||||
getOrThrow(cordaRPCConnection.getProxy().startFlowDynamic(SuperSimpleGenericFlow.class).getReturnValue());
|
||||
return null;
|
||||
}));
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
public static class SuperSimpleGenericFlow extends FlowLogic<GenericHolder<String>> {
|
||||
public SuperSimpleGenericFlow() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericHolder<String> call() {
|
||||
return new GenericHolder<>(IntStream.of(100).mapToObj((i) -> "" + i).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
public static class GenericHolder<S> {
|
||||
private final List<S> items;
|
||||
|
||||
public GenericHolder(List<S> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public List<S> getItems() {
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static <Y> Y getOrThrow(CordaFuture<Y> future) {
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,6 @@ import net.corda.node.services.Permissions
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
@ -48,7 +47,6 @@ class FlowRetryTest : IntegrationTest() {
|
||||
val user = User("mark", "dadada", setOf(Permissions.startFlow<InitiatorFlow>()))
|
||||
val result: Any? = driver(DriverParameters(
|
||||
startNodesInProcess = isQuasarAgentSpecified(),
|
||||
portAllocation = RandomFree,
|
||||
notarySpecs = emptyList()
|
||||
)) {
|
||||
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
|
@ -10,10 +10,6 @@
|
||||
package net.corda.node.persistence
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID
|
||||
import net.corda.testMessage.Message
|
||||
import net.corda.testMessage.MessageContract
|
||||
import net.corda.testMessage.MessageState
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.StateAndContract
|
||||
@ -30,11 +26,14 @@ import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID
|
||||
import net.corda.testMessage.Message
|
||||
import net.corda.testMessage.MessageContract
|
||||
import net.corda.testMessage.MessageState
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
@ -57,8 +56,11 @@ 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(inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(),
|
||||
portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
|
||||
val stateAndRef: StateAndRef<MessageState>? = driver(DriverParameters(
|
||||
inMemoryDB = false,
|
||||
startNodesInProcess = isQuasarAgentSpecified(),
|
||||
extraCordappPackagesToScan = listOf(MessageState::class.packageName)
|
||||
)) {
|
||||
val nodeName = {
|
||||
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||
val nodeName = nodeHandle.nodeInfo.singleIdentity().name
|
||||
@ -90,7 +92,11 @@ class NodeStatePersistenceTests : IntegrationTest() {
|
||||
|
||||
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
|
||||
val message = Message("Hello world!")
|
||||
val stateAndRef: StateAndRef<MessageState>? = driver(DriverParameters(inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(), portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
|
||||
val stateAndRef: StateAndRef<MessageState>? = driver(DriverParameters(
|
||||
inMemoryDB = false,
|
||||
startNodesInProcess = isQuasarAgentSpecified(),
|
||||
extraCordappPackagesToScan = listOf(MessageState::class.packageName)
|
||||
)) {
|
||||
val nodeName = {
|
||||
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||
val nodeName = nodeHandle.nodeInfo.singleIdentity().name
|
||||
|
@ -32,7 +32,6 @@ import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
@ -65,7 +64,7 @@ class TimedFlowMultiThreadedSMMTests : IntegrationTest() {
|
||||
@Test
|
||||
fun `timed flow is retried`() {
|
||||
val user = User("test", "pwd", setOf(Permissions.startFlow<TimedInitiatorFlow>(), Permissions.startFlow<SuperFlow>()))
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||
driver(DriverParameters(startNodesInProcess = true)) {
|
||||
val configOverrides = mapOf("flowTimeout" to mapOf(
|
||||
"timeout" to Duration.ofSeconds(3),
|
||||
"maxRestartCount" to 2,
|
||||
@ -86,7 +85,7 @@ class TimedFlowMultiThreadedSMMTests : IntegrationTest() {
|
||||
@Test
|
||||
fun `progress tracker is preserved after flow is retried`() {
|
||||
val user = User("test", "pwd", setOf(Permissions.startFlow<TimedInitiatorFlow>(), Permissions.startFlow<SuperFlow>()))
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||
driver(DriverParameters(startNodesInProcess = true)) {
|
||||
|
||||
val configOverrides = mapOf("flowTimeout" to mapOf(
|
||||
"timeout" to Duration.ofSeconds(2),
|
||||
|
@ -25,8 +25,8 @@ import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
@ -52,7 +52,7 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
|
||||
private val cacheTimeout = 1.seconds
|
||||
private val portAllocation = RandomFree
|
||||
private val portAllocation = PortAllocation.Incremental(10000)
|
||||
|
||||
private lateinit var networkMapServer: NetworkMapServer
|
||||
private lateinit var compatibilityZone: CompatibilityZoneParams
|
||||
|
@ -13,13 +13,13 @@ package net.corda.node.services.rpc
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.RPCException
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.messaging.ClientRpcSslOptions
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.Permissions.Companion.all
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.core.messaging.ClientRpcSslOptions
|
||||
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
|
||||
import net.corda.node.utilities.saveToKeyStore
|
||||
import net.corda.node.utilities.saveToTrustStore
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_RPC_USER
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
@ -27,7 +27,6 @@ import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
@ -55,7 +54,7 @@ class RpcSslTest : IntegrationTest() {
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
|
||||
private val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
|
||||
|
||||
@Test
|
||||
fun `RPC client using ssl is able to run a command`() {
|
||||
@ -70,7 +69,7 @@ class RpcSslTest : IntegrationTest() {
|
||||
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)
|
||||
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
val node = startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow()
|
||||
val client = CordaRPCClient.createWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions)
|
||||
val connection = client.start(user.username, user.password)
|
||||
@ -108,7 +107,7 @@ class RpcSslTest : IntegrationTest() {
|
||||
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert1)
|
||||
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
val node = startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow()
|
||||
Assertions.assertThatThrownBy {
|
||||
val connection = CordaRPCClient.createWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions).start(user.username, user.password)
|
||||
@ -128,7 +127,7 @@ class RpcSslTest : IntegrationTest() {
|
||||
fun `RPC client not using ssl can run commands`() {
|
||||
val user = User("mark", "dadada", setOf(all()))
|
||||
var successful = false
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
val node = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||
val connection = CordaRPCClient(node.rpcAddress).start(user.username, user.password)
|
||||
connection.proxy.apply {
|
||||
@ -148,7 +147,7 @@ class RpcSslTest : IntegrationTest() {
|
||||
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)
|
||||
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
val node = startNode(customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow()
|
||||
val client = CordaRPCClient.createWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions)
|
||||
|
||||
|
@ -13,13 +13,7 @@ package net.corda.node.services.statemachine
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.ReceiveTransactionFlow
|
||||
import net.corda.core.flows.SendTransactionFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.internal.InputStreamAndHash
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.messaging.startFlow
|
||||
@ -29,17 +23,12 @@ import net.corda.node.services.config.MB
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.node.User
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
@ -105,8 +94,8 @@ class LargeTransactionsTest : IntegrationTest() {
|
||||
driver(DriverParameters(
|
||||
startNodesInProcess = true,
|
||||
extraCordappPackagesToScan = listOf("net.corda.testing.contracts"),
|
||||
networkParameters = testNetworkParameters(maxTransactionSize = 13.MB.toInt()),
|
||||
portAllocation = RandomFree)) {
|
||||
networkParameters = testNetworkParameters(maxTransactionSize = 13.MB.toInt())
|
||||
)) {
|
||||
val rpcUser = User("admin", "admin", setOf("ALL"))
|
||||
val (alice, _) = listOf(ALICE_NAME, BOB_NAME).map { startNode(providedName = it, rpcUsers = listOf(rpcUser)) }.transpose().getOrThrow()
|
||||
CordaRPCClient(alice.rpcAddress).use(rpcUser.username, rpcUser.password) {
|
||||
|
@ -33,7 +33,6 @@ import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.internal.createNodeSslConfig
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
|
||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||
@ -46,7 +45,7 @@ import java.nio.file.Path
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
class ArtemisRpcTests {
|
||||
private val ports: PortAllocation = RandomFree
|
||||
private val ports: PortAllocation = PortAllocation.Incremental(10000)
|
||||
|
||||
private val user = User("mark", "dadada", setOf(all()))
|
||||
private val users = listOf(user)
|
||||
@ -106,8 +105,14 @@ class ArtemisRpcTests {
|
||||
}.isInstanceOf(RPCException::class.java)
|
||||
}
|
||||
|
||||
private fun testSslCommunication(nodeSSlconfig: SSLConfiguration, brokerSslOptions: BrokerRpcSslOptions?, useSslForBroker: Boolean, clientSslOptions: ClientRpcSslOptions?, address: NetworkHostAndPort = ports.nextHostAndPort(),
|
||||
adminAddress: NetworkHostAndPort = ports.nextHostAndPort(), baseDirectory: Path = tempFolder.root.toPath()) {
|
||||
private fun testSslCommunication(nodeSSlconfig: SSLConfiguration,
|
||||
brokerSslOptions: BrokerRpcSslOptions?,
|
||||
useSslForBroker: Boolean,
|
||||
clientSslOptions: ClientRpcSslOptions?,
|
||||
address: NetworkHostAndPort = ports.nextHostAndPort(),
|
||||
adminAddress: NetworkHostAndPort = ports.nextHostAndPort(),
|
||||
baseDirectory: Path = tempFolder.root.toPath()
|
||||
) {
|
||||
val maxMessageSize = 10000
|
||||
val jmxEnabled = false
|
||||
|
||||
|
@ -8,8 +8,10 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
buildscript {
|
||||
ext.strata_version = '1.1.2'
|
||||
allprojects {
|
||||
ext {
|
||||
strata_version = '1.1.2'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
@ -52,7 +54,7 @@ dependencies {
|
||||
|
||||
// The SIMM demo CorDapp depends upon Cash CorDapp features
|
||||
cordapp project(':finance')
|
||||
cordapp project(':samples:simm-valuation-demo:contracts-states')
|
||||
cordapp project(path: ':samples:simm-valuation-demo:contracts-states', configuration: 'shrinkArtifacts')
|
||||
cordapp project(':samples:simm-valuation-demo:flows')
|
||||
|
||||
// Corda integration dependencies
|
||||
@ -62,26 +64,26 @@ dependencies {
|
||||
cordaCompile project(':webserver')
|
||||
|
||||
// Javax is required for webapis
|
||||
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
|
||||
compile "org.glassfish.jersey.core:jersey-server:$jersey_version"
|
||||
|
||||
// Cordapp dependencies
|
||||
// Specify your cordapp's dependencies below, including dependent cordapps
|
||||
compile "com.opengamma.strata:strata-basics:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-product:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-data:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-calc:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-pricer:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-report:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-market:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-collect:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-loader:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-math:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-basics:$strata_version"
|
||||
compile "com.opengamma.strata:strata-product:$strata_version"
|
||||
compile "com.opengamma.strata:strata-data:$strata_version"
|
||||
compile "com.opengamma.strata:strata-calc:$strata_version"
|
||||
compile "com.opengamma.strata:strata-pricer:$strata_version"
|
||||
compile "com.opengamma.strata:strata-report:$strata_version"
|
||||
compile "com.opengamma.strata:strata-market:$strata_version"
|
||||
compile "com.opengamma.strata:strata-collect:$strata_version"
|
||||
compile "com.opengamma.strata:strata-loader:$strata_version"
|
||||
compile "com.opengamma.strata:strata-math:$strata_version"
|
||||
|
||||
// Test dependencies
|
||||
testCompile project(':node-driver')
|
||||
scenarioTestCompile project(path: ":experimental:behave", configuration: 'testArtifacts')
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||
}
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
|
@ -1,24 +1,76 @@
|
||||
buildscript {
|
||||
ext.strata_version = '1.1.2'
|
||||
apply plugin: 'net.corda.plugins.cordapp'
|
||||
|
||||
def javaHome = System.getProperty('java.home')
|
||||
def shrinkJar = file("$buildDir/libs/${project.name}-${project.version}-tiny.jar")
|
||||
|
||||
cordapp {
|
||||
info {
|
||||
vendor = 'R3'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'net.corda.plugins.cordapp'
|
||||
apply plugin: 'net.corda.plugins.cordformation'
|
||||
configurations {
|
||||
shrinkArtifacts
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
cordaCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
// The SIMM demo CorDapp depends upon Cash CorDapp features
|
||||
cordapp project(':finance')
|
||||
|
||||
// Corda integration dependencies
|
||||
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||
cordaCompile project(':core')
|
||||
|
||||
|
||||
// Cordapp dependencies
|
||||
// Specify your cordapp's dependencies below, including dependent cordapps
|
||||
compile "com.opengamma.strata:strata-product:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-market:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-product:$strata_version"
|
||||
compile "com.opengamma.strata:strata-market:$strata_version"
|
||||
}
|
||||
|
||||
}
|
||||
jar {
|
||||
classifier = 'fat'
|
||||
}
|
||||
|
||||
import proguard.gradle.ProGuardTask
|
||||
task shrink(type: ProGuardTask) {
|
||||
injars jar
|
||||
outjars shrinkJar
|
||||
|
||||
libraryjars "$javaHome/lib/rt.jar"
|
||||
libraryjars "$javaHome/lib/jce.jar"
|
||||
configurations.runtime.forEach {
|
||||
libraryjars it.path, filter: '!META-INF/versions/**'
|
||||
}
|
||||
|
||||
dontwarn 'afu.org.checkerframework.**'
|
||||
dontwarn 'co.paralleluniverse.**'
|
||||
dontwarn 'org.checkerframework.**'
|
||||
dontwarn 'org.joda.**'
|
||||
dontnote
|
||||
|
||||
// We need to preserve our CorDapp's own directory structure so that Corda
|
||||
// can find the contract classes.
|
||||
keepdirectories 'net/corda/**'
|
||||
keepattributes '*'
|
||||
dontobfuscate
|
||||
dontoptimize
|
||||
verbose
|
||||
|
||||
// These are our CorDapp classes, so don't change these.
|
||||
keep 'class net.corda.vega.** { *; }', includedescriptorclasses:true
|
||||
|
||||
// Until CorDapps are isolated from each other, we need to ensure that the
|
||||
// versions of the classes that this CorDapp needs are still usable by other
|
||||
// CorDapps. Unfortunately, this means that we cannot shrink them as much as
|
||||
// we'd like to.
|
||||
keepclassmembers 'class com.opengamma.strata.** { *; }', includedescriptorclasses:true
|
||||
keepclassmembers 'class com.google.** { *; }', includedescriptorclasses:true
|
||||
keepclassmembers 'class org.joda.** { *; }', includedescriptorclasses:true
|
||||
}
|
||||
jar.finalizedBy shrink
|
||||
|
||||
artifacts {
|
||||
shrinkArtifacts file: shrinkJar, name: project.name, type: 'jar', extension: 'jar', classifier: 'tiny', builtBy: shrink
|
||||
}
|
||||
|
@ -1,33 +1,32 @@
|
||||
buildscript {
|
||||
ext.strata_version = '1.1.2'
|
||||
}
|
||||
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.cordapp'
|
||||
apply plugin: 'net.corda.plugins.cordformation'
|
||||
|
||||
cordapp {
|
||||
info {
|
||||
vendor = 'R3'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
cordaCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
// The SIMM demo CorDapp depends upon Cash CorDapp features
|
||||
cordapp project(':finance')
|
||||
cordapp project(':samples:simm-valuation-demo:contracts-states')
|
||||
cordapp project(path: ':samples:simm-valuation-demo:contracts-states', configuration: 'shrinkArtifacts')
|
||||
|
||||
// Corda integration dependencies
|
||||
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||
cordaCompile project(':core')
|
||||
|
||||
// Cordapp dependencies
|
||||
// Specify your cordapp's dependencies below, including dependent cordapps
|
||||
compile "com.opengamma.strata:strata-basics:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-product:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-data:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-calc:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-pricer:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-report:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-market:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-collect:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-loader:${strata_version}"
|
||||
compile "com.opengamma.strata:strata-math:${strata_version}"
|
||||
|
||||
compile "com.opengamma.strata:strata-basics:$strata_version"
|
||||
compile "com.opengamma.strata:strata-product:$strata_version"
|
||||
compile "com.opengamma.strata:strata-data:$strata_version"
|
||||
compile "com.opengamma.strata:strata-calc:$strata_version"
|
||||
compile "com.opengamma.strata:strata-pricer:$strata_version"
|
||||
compile "com.opengamma.strata:strata-report:$strata_version"
|
||||
compile "com.opengamma.strata:strata-market:$strata_version"
|
||||
compile "com.opengamma.strata:strata-collect:$strata_version"
|
||||
compile "com.opengamma.strata:strata-loader:$strata_version"
|
||||
compile "com.opengamma.strata:strata-math:$strata_version"
|
||||
}
|
||||
|
@ -73,9 +73,13 @@ class CollectionSerializer(private val declaredType: ParameterizedType, factory:
|
||||
|
||||
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "list", Descriptor(typeDescriptor), emptyList())
|
||||
|
||||
private val outboundType = resolveTypeVariables(declaredType.actualTypeArguments[0], null)
|
||||
private val inboundType = declaredType.actualTypeArguments[0]
|
||||
|
||||
|
||||
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
|
||||
if (output.writeTypeNotations(typeNotation)) {
|
||||
output.requireSerializer(declaredType.actualTypeArguments[0])
|
||||
output.requireSerializer(outboundType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,12 +94,13 @@ class CollectionSerializer(private val declaredType: ParameterizedType, factory:
|
||||
data.withDescribed(typeNotation.descriptor) {
|
||||
withList {
|
||||
for (entry in obj as Collection<*>) {
|
||||
output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], context, debugIndent)
|
||||
output.writeObjectOrNull(entry, this, outboundType, context, debugIndent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun readObject(
|
||||
obj: Any,
|
||||
schemas: SerializationSchemas,
|
||||
@ -103,7 +108,7 @@ class CollectionSerializer(private val declaredType: ParameterizedType, factory:
|
||||
context: SerializationContext): Any = ifThrowsAppend({ declaredType.typeName }) {
|
||||
// TODO: Can we verify the entries in the list?
|
||||
concreteBuilder((obj as List<*>).map {
|
||||
input.readObjectOrNull(it, schemas, declaredType.actualTypeArguments[0], context)
|
||||
input.readObjectOrNull(it, schemas, inboundType, context)
|
||||
})
|
||||
}
|
||||
}
|
@ -80,10 +80,15 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
|
||||
|
||||
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "map", Descriptor(typeDescriptor), emptyList())
|
||||
|
||||
private val inboundKeyType = declaredType.actualTypeArguments[0]
|
||||
private val outboundKeyType = resolveTypeVariables(inboundKeyType, null)
|
||||
private val inboundValueType = declaredType.actualTypeArguments[1]
|
||||
private val outboundValueType = resolveTypeVariables(inboundValueType, null)
|
||||
|
||||
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
|
||||
if (output.writeTypeNotations(typeNotation)) {
|
||||
output.requireSerializer(declaredType.actualTypeArguments[0])
|
||||
output.requireSerializer(declaredType.actualTypeArguments[1])
|
||||
output.requireSerializer(outboundKeyType)
|
||||
output.requireSerializer(outboundValueType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,8 +106,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
|
||||
data.putMap()
|
||||
data.enter()
|
||||
for ((key, value) in obj as Map<*, *>) {
|
||||
output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], context, debugIndent)
|
||||
output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], context, debugIndent)
|
||||
output.writeObjectOrNull(key, data, outboundKeyType, context, debugIndent)
|
||||
output.writeObjectOrNull(value, data, outboundValueType, context, debugIndent)
|
||||
}
|
||||
data.exit() // exit map
|
||||
}
|
||||
@ -118,8 +123,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
|
||||
|
||||
private fun readEntry(schemas: SerializationSchemas, input: DeserializationInput, entry: Map.Entry<Any?, Any?>,
|
||||
context: SerializationContext
|
||||
) = input.readObjectOrNull(entry.key, schemas, declaredType.actualTypeArguments[0], context) to
|
||||
input.readObjectOrNull(entry.value, schemas, declaredType.actualTypeArguments[1], context)
|
||||
) = input.readObjectOrNull(entry.key, schemas, inboundKeyType, context) to
|
||||
input.readObjectOrNull(entry.value, schemas, inboundValueType, context)
|
||||
|
||||
// Cannot use * as a bound for EnumMap and EnumSet since * is not an enum. So, we use a sample enum instead.
|
||||
// We don't actually care about the type, we just need to make the compiler happier.
|
||||
|
@ -437,7 +437,7 @@ fun Data.writeReferencedObject(refObject: ReferencedObject) {
|
||||
exit() // exit described
|
||||
}
|
||||
|
||||
private fun resolveTypeVariables(actualType: Type, contextType: Type?): Type {
|
||||
fun resolveTypeVariables(actualType: Type, contextType: Type?): Type {
|
||||
val resolvedType = if (contextType != null) TypeToken.of(contextType).resolveType(actualType).type else actualType
|
||||
// TODO: surely we check it is concrete at this point with no TypeVariables
|
||||
return if (resolvedType is TypeVariable<*>) {
|
||||
|
@ -10,27 +10,181 @@
|
||||
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.amqp.custom.BigIntegerSerializer;
|
||||
import net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.jgroups.util.Util.assertEquals;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class JavaGenericsTest {
|
||||
private static class Inner {
|
||||
private final Integer v;
|
||||
|
||||
private Inner(Integer v) { this.v = v; }
|
||||
Integer getV() { return v; }
|
||||
private Inner(Integer v) {
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
Integer getV() {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
private static class A<T> {
|
||||
private final T t;
|
||||
|
||||
private A(T t) { this.t = t; }
|
||||
public T getT() { return t; }
|
||||
private A(T t) {
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
public T getT() {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
private static class ConcreteClass {
|
||||
private final String theItem;
|
||||
|
||||
private ConcreteClass(String theItem) {
|
||||
this.theItem = theItem;
|
||||
}
|
||||
|
||||
public String getTheItem() {
|
||||
return theItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ConcreteClass that = (ConcreteClass) o;
|
||||
return Objects.equals(theItem, that.theItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(theItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@CordaSerializable
|
||||
private static class GenericClassWithList<CC> {
|
||||
private final List<CC> items;
|
||||
|
||||
private GenericClassWithList(List<CC> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public List<CC> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GenericClassWithList<?> that = (GenericClassWithList<?>) o;
|
||||
return Objects.equals(items, that.items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(items);
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
private static class GenericClassWithMap<CC, GG> {
|
||||
private final Map<CC, GG> theMap;
|
||||
|
||||
private GenericClassWithMap(Map<CC, GG> theMap) {
|
||||
this.theMap = new LinkedHashMap<>(theMap);
|
||||
}
|
||||
|
||||
public Map<CC, GG> getTheMap() {
|
||||
return theMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GenericClassWithMap<?, ?> that = (GenericClassWithMap<?, ?>) o;
|
||||
return Objects.equals(theMap, that.theMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(theMap);
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
private static class HolderOfGeneric<G> {
|
||||
private final G theGeneric;
|
||||
|
||||
private HolderOfGeneric(G theGeneric) {
|
||||
this.theGeneric = theGeneric;
|
||||
}
|
||||
|
||||
public G getTheGeneric() {
|
||||
return theGeneric;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HolderOfGeneric<?> that = (HolderOfGeneric<?>) o;
|
||||
return Objects.equals(theGeneric, that.theGeneric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(theGeneric);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
public void shouldSupportNestedGenericsFromJavaWithCollections() throws NotSerializableException {
|
||||
ConcreteClass concreteClass = new ConcreteClass("How to make concrete, $99/class");
|
||||
HolderOfGeneric<GenericClassWithList<ConcreteClass>> genericList = new HolderOfGeneric<>(new GenericClassWithList<>(Collections.singletonList(concreteClass)));
|
||||
SerializerFactory factory = AMQPTestUtilsKt.testDefaultFactoryWithWhitelist();
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
SerializedBytes<?> bytes = ser.serialize(genericList, TestSerializationContext.testSerializationContext);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
HolderOfGeneric<GenericClassWithList<ConcreteClass>> genericList2 = des.deserialize(bytes, HolderOfGeneric.class, TestSerializationContext.testSerializationContext);
|
||||
Assert.assertThat(genericList, CoreMatchers.is(CoreMatchers.equalTo(genericList2)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportNestedGenericsFromJavaWithMaps() throws NotSerializableException {
|
||||
ConcreteClass concreteClass = new ConcreteClass("How to make concrete, $99/class");
|
||||
GenericClassWithMap<ConcreteClass, BigInteger> genericMap = new GenericClassWithMap<>(Collections.singletonMap(concreteClass, BigInteger.ONE));
|
||||
SerializerFactory factory = AMQPTestUtilsKt.testDefaultFactoryWithWhitelist();
|
||||
factory.register(BigIntegerSerializer.INSTANCE);
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
SerializedBytes<?> bytes = ser.serialize(genericMap, TestSerializationContext.testSerializationContext);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
GenericClassWithMap<ConcreteClass, BigInteger> genericMap2 = des.deserialize(bytes, GenericClassWithMap.class, TestSerializationContext.testSerializationContext);
|
||||
Assert.assertThat(genericMap2, CoreMatchers.is(CoreMatchers.equalTo(genericMap2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -77,7 +231,7 @@ public class JavaGenericsTest {
|
||||
@Test
|
||||
public void forceWildcard() throws NotSerializableException {
|
||||
SerializedBytes<?> bytes = forceWildcardSerialize(new A<>(new Inner(29)));
|
||||
Inner i = (Inner)forceWildcardDeserialize(bytes).getT();
|
||||
Inner i = (Inner) forceWildcardDeserialize(bytes).getT();
|
||||
assertEquals(29, i.getV());
|
||||
}
|
||||
|
||||
@ -86,7 +240,7 @@ public class JavaGenericsTest {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializedBytes<?> bytes = forceWildcardSerializeFactory(new A<>(new Inner(29)), factory);
|
||||
Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT();
|
||||
Inner i = (Inner) forceWildcardDeserializeFactory(bytes, factory).getT();
|
||||
|
||||
assertEquals(29, i.getV());
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.http.HttpApi
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
@ -109,15 +108,6 @@ class DriverTests : IntegrationTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `random free port allocation`() {
|
||||
val nodeHandle = driver(DriverParameters(portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
val nodeInfo = startNode(providedName = DUMMY_BANK_A_NAME)
|
||||
nodeMustBeUp(nodeInfo)
|
||||
}
|
||||
nodeMustBeDown(nodeHandle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `debug mode enables debug logging level`() {
|
||||
// Make sure we're using the log4j2 config which writes to the log file
|
||||
|
@ -22,11 +22,8 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.driver.InProcess
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.OutOfProcess
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.node.User
|
||||
import rx.Observable
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.ServerSocket
|
||||
import java.nio.file.Path
|
||||
|
||||
interface NodeHandleInternal : NodeHandle {
|
||||
@ -88,12 +85,3 @@ data class InProcessImpl(
|
||||
}
|
||||
|
||||
val InProcess.internalServices: StartedNodeServices get() = services as StartedNodeServices
|
||||
|
||||
object RandomFree : PortAllocation() {
|
||||
override fun nextPort(): Int {
|
||||
return ServerSocket().use {
|
||||
it.bind(InetSocketAddress(0))
|
||||
it.localPort
|
||||
}
|
||||
}
|
||||
}
|
@ -314,12 +314,17 @@ class CashViewer : CordaView("Cash") {
|
||||
tickLabelFormatter = stringConverter {
|
||||
Instant.ofEpochMilli(it.toLong()).atZone(TimeZone.getDefault().toZoneId()).toLocalTime().toString()
|
||||
}
|
||||
label= "Timeline"
|
||||
style = "-fx-font-size: 10px;"
|
||||
|
||||
}
|
||||
val yAxis = NumberAxis().apply {
|
||||
isAutoRanging = true
|
||||
isMinorTickVisible = false
|
||||
isForceZeroInRange = false
|
||||
tickLabelFormatter = stringConverter { it.toStringWithSuffix() }
|
||||
label= "Cash"
|
||||
style = "-fx-font-size: 10px;"
|
||||
}
|
||||
linechart(null, xAxis, yAxis) {
|
||||
series("USD") {
|
||||
|
@ -15,16 +15,16 @@ import com.jcraft.jsch.ChannelExec
|
||||
import com.jcraft.jsch.JSch
|
||||
import net.corda.client.rpc.RPCException
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.messaging.ClientRpcSslOptions
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.node.services.Permissions.Companion.all
|
||||
import net.corda.node.services.config.shell.toShellConfig
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.core.messaging.ClientRpcSslOptions
|
||||
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
|
||||
import net.corda.node.utilities.saveToKeyStore
|
||||
import net.corda.node.utilities.saveToTrustStore
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
@ -32,7 +32,6 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
@ -67,7 +66,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
|
||||
@Test
|
||||
fun `shell should not log in with invalid credentials`() {
|
||||
val user = User("u", "p", setOf())
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
val nodeFuture = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user), startInSameProcess = true)
|
||||
val node = nodeFuture.getOrThrow()
|
||||
|
||||
@ -108,7 +107,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
|
||||
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)
|
||||
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
||||
|
||||
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
||||
@ -136,7 +135,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
|
||||
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert1)
|
||||
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
||||
|
||||
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
||||
@ -153,7 +152,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
|
||||
|
||||
@Test
|
||||
fun `internal shell user should not be able to connect if node started with devMode=false`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
startNode().getOrThrow().use { node ->
|
||||
val conf = (node as NodeHandleInternal).configuration.toShellConfig()
|
||||
InteractiveShell.startShellInternal(conf)
|
||||
@ -219,7 +218,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
|
||||
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||
|
||||
var successful = false
|
||||
driver(DriverParameters(startNodesInProcess = true, portAllocation = RandomFree, notarySpecs = emptyList())) {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
||||
|
||||
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user