Merge pull request #1280 from corda/chrisr3-os-merge

Merge up to e879de70f306af30a80ecbd125dab799aeeaf268.
This commit is contained in:
Chris Rankin 2018-07-18 15:35:54 +01:00 committed by GitHub
commit 1b05ccd397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 2207 additions and 453 deletions

View File

@ -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-')) {

View File

@ -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) }

View File

@ -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)

View File

@ -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
)
/*

View File

@ -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)
}

View File

@ -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)

View File

@ -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)))
}
}

View File

@ -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())
}
}
}
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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 }
}
}

View File

@ -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)))
}

View File

@ -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"

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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"

View File

@ -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"]
}
}

View File

@ -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 }
}

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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}")
}
}

View 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``.

View File

@ -13,4 +13,5 @@ Networks
azure-vm-explore
aws-vm-explore
gcp-vm
deploy-locally
cipher-suites

View 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.

View File

@ -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')
}

View File

@ -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;
});
}
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 transactions 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`.

View File

@ -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.

View File

@ -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
----------------------

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

View File

@ -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>`_.

View File

@ -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.

View File

@ -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) {

View File

@ -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);
}
}
}

View File

@ -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()

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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']) {

View File

@ -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
}

View File

@ -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"
}

View File

@ -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)
})
}
}

View File

@ -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.

View File

@ -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<*>) {

View File

@ -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());
}

View File

@ -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

View 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
}
}
}

View File

@ -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") {

View File

@ -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(),