Merge remote-tracking branch 'open/master'

This commit is contained in:
Andras Slemmer 2017-03-22 13:36:27 +00:00
commit 8665e92960
266 changed files with 3985 additions and 2310 deletions

5
.gitignore vendored
View File

@ -81,3 +81,8 @@ docs/virtualenv/
# bft-smart
node/bft-smart-config/currentView
node/config/currentView
# Files you may find useful to have in your working directory.
PLAN
NOTES
TODO

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role=RECIPIENT --certificates=&quot;build/attachment-demo-nodes/Bank B/certificates&quot;" />
<option name="PROGRAM_PARAMETERS" value="--role=RECIPIENT" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role SENDER --certificates=&quot;build/attachment-demo-nodes/Bank A/certificates&quot;" />
<option name="PROGRAM_PARAMETERS" value="--role SENDER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role BUYER --certificates=&quot;build/trader-demo-nodes/Bank A/certificates&quot;" />
<option name="PROGRAM_PARAMETERS" value="--role BUYER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role SELLER --certificates=&quot;build/trader-demo-nodes/Bank B/certificates&quot;" />
<option name="PROGRAM_PARAMETERS" value="--role SELLER" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />

View File

@ -10,7 +10,7 @@ buildscript {
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.
//
// TODO: Sort this alphabetically.
ext.kotlin_version = '1.0.6'
ext.kotlin_version = '1.0.7'
ext.quasar_version = '0.7.6' // TODO: Upgrade to 0.7.7+ when Quasar bug 238 is resolved.
ext.asm_version = '0.5.3'
ext.artemis_version = '1.5.3'
@ -19,6 +19,7 @@ buildscript {
ext.jersey_version = '2.25'
ext.jolokia_version = '2.0.0-M3'
ext.assertj_version = '3.6.1'
ext.slf4j_version = '1.7.22'
ext.log4j_version = '2.7'
ext.bouncycastle_version = '1.56'
ext.guava_version = '19.0'
@ -30,7 +31,7 @@ buildscript {
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final'
ext.h2_version = '1.4.193'
ext.h2_version = '1.4.194'
ext.rxjava_version = '1.2.4'
ext.requery_version = '1.1.1'
ext.dokka_version = '0.9.13'
@ -119,8 +120,17 @@ repositories {
dependencies {
compile project(':node')
compile "com.google.guava:guava:$guava_version"
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
runtime project(path: ":node:webserver:webcapsule", configuration: 'runtimeArtifacts')
// For the buildCordappDependenciesJar task
runtime project(':client:jfx')
runtime project(':client:mock')
runtime project(':core')
runtime project(':finance')
runtime project(':node:webserver')
testCompile project(':test-utils')
}
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
@ -155,23 +165,25 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
name "Controller"
nearestCity "London"
advertisedServices = ["corda.notary.validating"]
artemisPort 10002
p2pPort 10002
cordapps = []
}
node {
name "Bank A"
nearestCity "London"
advertisedServices = []
artemisPort 10004
webPort 10005
p2pPort 10012
rpcPort 10013
webPort 10014
cordapps = []
}
node {
name "Bank B"
nearestCity "New York"
advertisedServices = []
artemisPort 10006
webPort 10007
p2pPort 10007
rpcPort 10008
webPort 10009
cordapps = []
}
}
@ -186,7 +198,7 @@ bintrayConfig {
projectUrl = 'https://github.com/corda/corda'
gpgSign = true
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
publications = ['client', 'core', 'corda', 'corda-webserver', 'finance', 'node', 'node-schemas', 'test-utils', 'jackson']
publications = ['jfx', 'mock', 'core', 'corda', 'corda-webserver', 'finance', 'node', 'node-api', 'node-schemas', 'test-utils', 'jackson', 'webserver']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'
@ -205,7 +217,7 @@ dokka {
moduleName = 'corda'
outputDirectory = 'docs/build/html/api/kotlin'
processConfigurations = ['compile']
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin', 'client/jackson/src/main/kotlin')
sourceDirs = files('core/src/main/kotlin', 'client/jfx/src/main/kotlin', 'client/mock/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin', 'client/jackson/src/main/kotlin')
}
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
@ -213,7 +225,20 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = "javadoc"
outputDirectory = 'docs/build/html/api/javadoc'
processConfigurations = ['compile']
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin', 'client/jackson/src/main/kotlin')
sourceDirs = files('core/src/main/kotlin', 'client/jfx/src/main/kotlin', 'client/mock/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin', 'client/jackson/src/main/kotlin')
}
task apidocs(dependsOn: ['dokka', 'dokkaJavadoc'])
// Build a ZIP of all JARs required to compile the Cordapp template
// Note: corda.jar is used at runtime so no runtime ZIP is necessary.
// Resulting ZIP can be found in "build/distributions"
task buildCordappDependenciesZip(type: Zip) {
baseName 'corda-deps'
from configurations.runtime
from configurations.compile
from configurations.testCompile
from buildscript.configurations.classpath
from 'node/capsule/NOTICE' // CDDL notice
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

View File

@ -14,6 +14,13 @@ repositories {
dependencies {
compile project(':core')
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile ("com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}")
// Jackson and its plugins: parsing to/from JSON and other textual formats.
compile "com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}"
// Yaml is useful for parsing strings to method calls.
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version"
// This adds support for java.time types.
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
testCompile "junit:junit:$junit_version"
}

View File

@ -1,25 +1,24 @@
package net.corda.jackson
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import net.corda.core.contracts.Amount
import net.corda.core.contracts.BusinessCalendar
import net.corda.core.crypto.*
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAPublicKey
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
/**
* Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
@ -28,6 +27,7 @@ import java.time.LocalDateTime
* Note that Jackson can also be used to serialise/deserialise other formats such as Yaml and XML.
*/
object JacksonSupport {
// TODO: This API could use some tidying up - there should really only need to be one kind of mapper.
// If you change this API please update the docs in the docsite (json.rst)
interface PartyObjectMapper {
@ -35,28 +35,19 @@ object JacksonSupport {
fun partyFromKey(owningKey: CompositeKey): Party?
}
class RpcObjectMapper(val rpc: CordaRPCOps) : PartyObjectMapper, ObjectMapper() {
class RpcObjectMapper(val rpc: CordaRPCOps, factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
override fun partyFromName(partyName: String): Party? = rpc.partyFromName(partyName)
override fun partyFromKey(owningKey: CompositeKey): Party? = rpc.partyFromKey(owningKey)
}
class IdentityObjectMapper(val identityService: IdentityService) : PartyObjectMapper, ObjectMapper(){
class IdentityObjectMapper(val identityService: IdentityService, factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
override fun partyFromName(partyName: String): Party? = identityService.partyFromName(partyName)
override fun partyFromKey(owningKey: CompositeKey): Party? = identityService.partyFromKey(owningKey)
}
class NoPartyObjectMapper: PartyObjectMapper, ObjectMapper() {
class NoPartyObjectMapper(factory: JsonFactory): PartyObjectMapper, ObjectMapper(factory) {
override fun partyFromName(partyName: String): Party? = throw UnsupportedOperationException()
override fun partyFromKey(owningKey: CompositeKey): Party? = throw UnsupportedOperationException()
}
val javaTimeModule: Module by lazy {
SimpleModule("java.time").apply {
addSerializer(LocalDate::class.java, ToStringSerializer)
addDeserializer(LocalDate::class.java, LocalDateDeserializer)
addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
addSerializer(LocalDateTime::class.java, ToStringSerializer)
}
}
val cordaModule: Module by lazy {
SimpleModule("core").apply {
addSerializer(AnonymousParty::class.java, AnonymousPartySerializer)
@ -66,8 +57,8 @@ object JacksonSupport {
addSerializer(BigDecimal::class.java, ToStringSerializer)
addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
addSerializer(SecureHash::class.java, SecureHashSerializer)
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
addSerializer(SecureHash.SHA256::class.java, SecureHashSerializer)
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
@ -83,27 +74,35 @@ object JacksonSupport {
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
addSerializer(NodeInfo::class.java, NodeInfoSerializer)
addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
// For Amount
addSerializer(Amount::class.java, AmountSerializer)
addDeserializer(Amount::class.java, AmountDeserializer)
// For OpaqueBytes
addDeserializer(OpaqueBytes::class.java, OpaqueBytesDeserializer)
addSerializer(OpaqueBytes::class.java, OpaqueBytesSerializer)
}
}
/** Mapper requiring RPC support to deserialise parties from names */
@JvmStatic
fun createDefaultMapper(rpc: CordaRPCOps): ObjectMapper = configureMapper(RpcObjectMapper(rpc))
@JvmStatic @JvmOverloads
fun createDefaultMapper(rpc: CordaRPCOps, factory: JsonFactory = JsonFactory()): ObjectMapper = configureMapper(RpcObjectMapper(rpc, factory))
/** For testing or situations where deserialising parties is not required */
@JvmStatic
fun createNonRpcMapper(): ObjectMapper = configureMapper(NoPartyObjectMapper())
@JvmStatic @JvmOverloads
fun createNonRpcMapper(factory: JsonFactory = JsonFactory()): ObjectMapper = configureMapper(NoPartyObjectMapper(factory))
/** For testing with an in memory identity service */
@JvmStatic
fun createInMemoryMapper(identityService: IdentityService) = configureMapper(IdentityObjectMapper(identityService))
@JvmStatic @JvmOverloads
fun createInMemoryMapper(identityService: IdentityService, factory: JsonFactory = JsonFactory()) = configureMapper(IdentityObjectMapper(identityService, factory))
private fun configureMapper(mapper: ObjectMapper): ObjectMapper = mapper.apply {
enable(SerializationFeature.INDENT_OUTPUT)
enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
registerModule(javaTimeModule)
registerModule(JavaTimeModule())
registerModule(cordaModule)
registerModule(KotlinModule())
}
@ -114,23 +113,6 @@ object JacksonSupport {
}
}
object LocalDateDeserializer : JsonDeserializer<LocalDate>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDate {
return try {
LocalDate.parse(parser.text)
} catch (e: Exception) {
throw JsonParseException(parser, "Invalid LocalDate ${parser.text}: ${e.message}")
}
}
}
object LocalDateKeyDeserializer : KeyDeserializer() {
override fun deserializeKey(text: String, p1: DeserializationContext): Any? {
return LocalDate.parse(text)
}
}
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.owningKey.toBase58String())
@ -143,7 +125,6 @@ object JacksonSupport {
parser.nextToken()
}
val mapper = parser.codec as PartyObjectMapper
// TODO this needs to use some industry identifier(s) instead of these keys
val key = CompositeKey.parseFromBase58(parser.text)
return AnonymousParty(key)
@ -253,5 +234,43 @@ object JacksonSupport {
}
}
}
object AmountSerializer : JsonSerializer<Amount<*>>() {
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toString())
}
}
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Amount<*> {
try {
return Amount.parseCurrency(parser.text)
} catch (e: Exception) {
try {
val tree = parser.readValueAsTree<JsonNode>()
require(tree["quantity"].canConvertToLong() && tree["token"].asText().isNotBlank())
val quantity = tree["quantity"].asLong()
val token = tree["token"].asText()
// Attempt parsing as a currency token. TODO: This needs thought about how to extend to other token types.
val currency = Currency.getInstance(token)
return Amount(quantity, currency)
} catch(e2: Exception) {
throw JsonParseException(parser, "Invalid amount ${parser.text}", e2)
}
}
}
}
object OpaqueBytesDeserializer : JsonDeserializer<OpaqueBytes>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): OpaqueBytes {
return OpaqueBytes(parser.text.toByteArray())
}
}
object OpaqueBytesSerializer : JsonSerializer<OpaqueBytes>() {
override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeBinary(value.bytes)
}
}
}

View File

@ -0,0 +1,184 @@
package net.corda.jackson
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import net.corda.jackson.StringToMethodCallParser.ParsedMethodCall
import org.slf4j.LoggerFactory
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.util.concurrent.Callable
import javax.annotation.concurrent.ThreadSafe
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KotlinReflectionInternalError
import kotlin.reflect.jvm.kotlinFunction
/**
* This class parses strings in a format designed for human usability into [ParsedMethodCall] objects representing a
* ready-to-invoke call on the given target object. The strings accepted by this class are a minor variant of
* [Yaml](http://www.yaml.org/spec/1.2/spec.html) and can be easily typed at a command line. Intended use cases include
* things like the Corda shell, text-based RPC dispatch, simple scripting and so on.
*
* # Syntax
*
* The format of the string is as follows. The first word is the name of the method and must always be present. The rest,
* which is optional, is wrapped in curly braces and parsed as if it were a Yaml object. The keys of this object are then
* mapped to the parameters of the method via the usual Jackson mechanisms. The standard [java.lang.Object] methods are
* excluded.
*
* One convenient feature of Yaml is that barewords collapse into strings, thus you can write a call like the following:
*
* fun someCall(note: String, option: Boolean)
*
* someCall note: This is a really helpful feature, option: true
*
* ... and it will be parsed in the intuitive way. Quotes are only needed if you want to put a comma into the string.
*
* There is an [online Yaml parser](http://yaml-online-parser.appspot.com/) which can be used to explore
* the allowed syntax.
*
* # Usage
*
* This class is thread safe. Multiple strings may be parsed in parallel, and the resulting [ParsedMethodCall]
* objects may be reused multiple times and also invoked in parallel, as long as the underling target object is
* thread safe itself.
*
* You may pass in an alternative [ObjectMapper] to control what types can be parsed, but it must be configured
* with the [YAMLFactory] for the class to work.
*
* # Limitations
*
* - The target class must be either a Kotlin class, or a Java class compiled with the -parameters command line
* switch, as the class relies on knowing the names of parameters which isn't data provided by default by the
* Java compiler.
* - Vararg methods are not supported, as the type information that'd be required is missing.
* - Method overloads that have identical parameter names but different types can't be handled, because often
* a string could map to multiple types, so which one to use is ambiguous. If you want your interface to be
* usable with this utility make sure the parameter and method names don't rely on type overloading.
*
* # Examples
*
* fun simple() = ...
* "simple" -> runs the no-args function 'simple'
*
* fun attachmentExists(id: SecureHash): Boolean
* "attachmentExists id: b6d7e826e87" -> parses the given ID as a SecureHash
*
* fun addNote(id: SecureHash, note: String)
* "addNote id: b6d7e826e8739ab2eb6e077fc4fba9b04fb880bb4cbd09bc618d30234a8827a4, note: Some note"
*/
@ThreadSafe
open class StringToMethodCallParser<in T : Any>(targetType: Class<out T>,
private val om: ObjectMapper = JacksonSupport.createNonRpcMapper(YAMLFactory())) {
/** Same as the regular constructor but takes a Kotlin reflection [KClass] instead of a Java [Class]. */
constructor(targetType: KClass<out T>) : this(targetType.java)
companion object {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private val ignoredNames = Object::class.java.methods.map { it.name }
private fun methodsFromType(clazz: Class<*>) = clazz.methods.map { it.name to it }.toMap().filterKeys { it !in ignoredNames }
private val log = LoggerFactory.getLogger(StringToMethodCallParser::class.java)!!
}
/** The methods that can be invoked via this parser. */
protected val methodMap = methodsFromType(targetType)
/** A map of method name to parameter names for the target type. */
val methodParamNames: Map<String, List<String>> = targetType.declaredMethods.mapNotNull {
try {
it.name to paramNamesFromMethod(it)
} catch(e: KotlinReflectionInternalError) {
// Kotlin reflection doesn't support every method that can exist on an object (in particular, reified
// inline methods) so we just ignore those here.
null
}
}.toMap()
inner class ParsedMethodCall(private val target: T?, val methodName: String, val args: Array<Any?>) : Callable<Any?> {
operator fun invoke(): Any? = call()
override fun call(): Any? {
if (target == null)
throw IllegalStateException("No target object was specified")
if (log.isDebugEnabled)
log.debug("Invoking call $methodName($args)")
return methodMap[methodName]!!.invoke(target, *args)
}
}
/**
* Uses either Kotlin or Java 8 reflection to learn the names of the parameters to a method.
*/
open fun paramNamesFromMethod(method: Method): List<String> {
val kf: KFunction<*>? = method.kotlinFunction
return method.parameters.mapIndexed { index, param ->
when {
param.isNamePresent -> param.name
// index + 1 because the first Kotlin reflection param is 'this', but that doesn't match Java reflection.
kf != null -> kf.parameters[index + 1].name ?: throw UnparseableCallException.ReflectionDataMissing(method.name, index)
else -> throw UnparseableCallException.ReflectionDataMissing(method.name, index)
}
}
}
/**
* Uses either Kotlin or Java 8 reflection to learn the names of the parameters to a constructor.
*/
open fun paramNamesFromConstructor(ctor: Constructor<*>): List<String> {
val kf: KFunction<*>? = ctor.kotlinFunction
return ctor.parameters.mapIndexed { index, param ->
when {
param.isNamePresent -> param.name
kf != null -> kf.parameters[index].name ?: throw UnparseableCallException.ReflectionDataMissing("<init>", index)
else -> throw UnparseableCallException.ReflectionDataMissing("<init>", index)
}
}
}
open class UnparseableCallException(command: String) : Exception("Could not parse as a command: $command") {
class UnknownMethod(val methodName: String) : UnparseableCallException("Unknown command name: $methodName")
class MissingParameter(methodName: String, val paramName: String, command: String) : UnparseableCallException("Parameter $paramName missing from attempt to invoke $methodName in command: $command")
class TooManyParameters(methodName: String, command: String) : UnparseableCallException("Too many parameters provided for $methodName: $command")
class ReflectionDataMissing(methodName: String, argIndex: Int) : UnparseableCallException("Method $methodName missing parameter name at index $argIndex")
}
/**
* Parses the given command as a call on the target type. The target should be specified, if it's null then
* the resulting [ParsedMethodCall] can't be invoked, just inspected.
*/
@Throws(UnparseableCallException::class)
fun parse(target: T?, command: String): ParsedMethodCall {
log.debug("Parsing call command from string: {}", command)
val spaceIndex = command.indexOf(' ')
val name = if (spaceIndex != -1) command.substring(0, spaceIndex) else command
val argStr = if (spaceIndex != -1) command.substring(spaceIndex) else ""
val method = methodMap[name] ?: throw UnparseableCallException.UnknownMethod(name)
log.debug("Parsing call for method {}", name)
val args = parseArguments(name, paramNamesFromMethod(method).zip(method.parameterTypes), argStr)
return ParsedMethodCall(target, name, args)
}
/**
* Parses only the arguments string given the info about parameter names and types.
*
* @param methodNameHint A name that will be used in exceptions if thrown; not used for any other purpose.
*/
@Throws(UnparseableCallException::class)
fun parseArguments(methodNameHint: String, parameters: List<Pair<String, Class<*>>>, args: String): Array<Any?> {
// If we have parameters, wrap them in {} to allow the Yaml parser to eat them on a single line.
val parameterString = "{ $args }"
val tree: JsonNode = om.readTree(parameterString) ?: throw UnparseableCallException(args)
if (tree.size() > parameters.size) throw UnparseableCallException.TooManyParameters(methodNameHint, args)
val inOrderParams: List<Any?> = parameters.mapIndexed { index, param ->
val (argName, argType) = param
val entry = tree[argName] ?: throw UnparseableCallException.MissingParameter(methodNameHint, argName, args)
om.readValue(entry.traverse(om), argType)
}
if (log.isDebugEnabled) {
inOrderParams.forEachIndexed { i, param ->
val tmp = if (param != null) "${param.javaClass.name} -> $param" else "(null)"
log.debug("Parameter $i. $tmp")
}
}
return inOrderParams.toTypedArray()
}
}

View File

@ -1,12 +1,17 @@
package net.corda.jackson
import com.fasterxml.jackson.databind.SerializationFeature
import com.pholser.junit.quickcheck.From
import com.pholser.junit.quickcheck.Property
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
import net.corda.core.contracts.Amount
import net.corda.core.contracts.USD
import net.corda.core.testing.PublicKeyGenerator
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.junit.Test
import org.junit.runner.RunWith
import java.security.PublicKey
import java.util.*
import kotlin.test.assertEquals
@RunWith(JUnitQuickcheck::class)
@ -21,4 +26,28 @@ class JacksonSupportTest {
val parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java)
assertEquals(publicKey, parsedKey)
}
private class Dummy(val notional: Amount<Currency>)
@Test
fun readAmount() {
val oldJson = """
{
"notional": {
"quantity": 2500000000,
"token": "USD"
}
}
"""
val newJson = """ { "notional" : "$25000000" } """
assertEquals(Amount(2500000000L, USD), mapper.readValue(newJson, Dummy::class.java).notional)
assertEquals(Amount(2500000000L, USD), mapper.readValue(oldJson, Dummy::class.java).notional)
}
@Test
fun writeAmount() {
val writer = mapper.writer().without(SerializationFeature.INDENT_OUTPUT)
assertEquals("""{"notional":"25000000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$25000000"))))
}
}

View File

@ -0,0 +1,34 @@
package net.corda.jackson
import net.corda.core.crypto.SecureHash
import org.junit.Test
import kotlin.test.assertEquals
class StringToMethodCallParserTest {
@Suppress("UNUSED")
class Target {
fun simple() = "simple"
fun string(note: String) = note
fun twoStrings(a: String, b: String) = a + b
fun simpleObject(hash: SecureHash.SHA256) = hash.toString()!!
fun complexObject(pair: Pair<Int, String>) = pair
}
val randomHash = "361170110f61086f77ff2c5b7ab36513705da1a3ebabf14dbe5cc9c982c45401"
val tests = mapOf(
"simple" to "simple",
"string note: A test of barewords" to "A test of barewords",
"twoStrings a: Some words, b: ' and some words, like, Kirk, would, speak'" to "Some words and some words, like, Kirk, would, speak",
"simpleObject hash: $randomHash" to randomHash.toUpperCase(),
"complexObject pair: { first: 12, second: Word up brother }" to Pair(12, "Word up brother")
)
@Test
fun calls() {
val parser = StringToMethodCallParser(Target::class)
val target = Target()
for ((input, output) in tests) {
assertEquals(output, parser.parse(target, input).invoke())
}
}
}

View File

@ -2,7 +2,7 @@ apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
description 'Corda client modules'
description 'Corda client JavaFX modules'
repositories {
mavenLocal()
@ -35,7 +35,7 @@ sourceSets {
}
test {
resources {
srcDir "../config/test"
srcDir "../../config/test"
}
}
}
@ -47,10 +47,6 @@ dependencies {
compile project(":core")
compile project(':node')
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
compile "com.google.guava:guava:$guava_version"
// ReactFX: Functional reactive UI programming.

View File

@ -1,7 +1,6 @@
package net.corda.client
package net.corda.client.jfx
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.issuedBy
import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow
@ -12,7 +11,6 @@ import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.internal.Node
import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService
@ -37,7 +35,7 @@ class CordaRPCClientTest : NodeBasedTest() {
@Before
fun setUp() {
node = startNode("Alice", rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
client = CordaRPCClient(node.configuration.artemisAddress, configureTestSSL())
client = CordaRPCClient(node.configuration.rpcAddress!!)
}
@After
@ -85,10 +83,7 @@ class CordaRPCClientTest : NodeBasedTest() {
fun `FlowException thrown by flow`() {
client.start(rpcUser.username, rpcUser.password)
val proxy = client.proxy()
val handle = proxy.startFlow(::CashPaymentFlow,
100.DOLLARS.issuedBy(node.info.legalIdentity.ref(1)),
node.info.legalIdentity
)
val handle = proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.legalIdentity)
// TODO Restrict this to CashException once RPC serialisation has been fixed
assertThatExceptionOfType(FlowException::class.java).isThrownBy {
handle.returnValue.getOrThrow()

View File

@ -1,11 +1,10 @@
package net.corda.client
package net.corda.client.jfx
import net.corda.client.model.NodeMonitorModel
import net.corda.client.model.ProgressTrackingEvent
import net.corda.client.jfx.model.NodeMonitorModel
import net.corda.client.jfx.model.ProgressTrackingEvent
import net.corda.core.bufferUntilSubscribed
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.USD
import net.corda.core.flows.StateMachineRunId
import net.corda.core.getOrThrow
@ -22,16 +21,14 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashExitFlow
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.driver.DriverBasedTest
import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.testing.node.DriverBasedTest
import net.corda.testing.sequence
import org.junit.Test
import rx.Observable
@ -57,9 +54,10 @@ class NodeMonitorModelTest : DriverBasedTest() {
)
val aliceNodeFuture = startNode("Alice", rpcUsers = listOf(cashUser))
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
aliceNode = aliceNodeFuture.getOrThrow().nodeInfo
notaryNode = notaryNodeFuture.getOrThrow().nodeInfo
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
val notaryNodeHandle = notaryNodeFuture.getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo
notaryNode = notaryNodeHandle.nodeInfo
newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
@ -70,7 +68,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
monitor.register(ArtemisMessagingComponent.toHostAndPort(aliceNode.address), configureTestSSL(), cashUser.username, cashUser.password)
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password)
rpc = monitor.proxyObservable.value!!
runTest()
}
@ -124,17 +122,8 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test
fun `cash issue and move`() {
rpc.startFlow(::CashIssueFlow,
Amount(100, USD),
OpaqueBytes(ByteArray(1, { 1 })),
aliceNode.legalIdentity,
notaryNode.notaryIdentity
).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow,
Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
aliceNode.legalIdentity
)
rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), aliceNode.legalIdentity, notaryNode.notaryIdentity).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.legalIdentity).returnValue.getOrThrow()
var issueSmId: StateMachineRunId? = null
var moveSmId: StateMachineRunId? = null

View File

@ -1,10 +1,10 @@
package net.corda.client.model
package net.corda.client.jfx.model
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import net.corda.client.fxutils.fold
import net.corda.client.fxutils.map
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef

View File

@ -1,4 +1,4 @@
package net.corda.client.model
package net.corda.client.jfx.model
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue

View File

@ -1,4 +1,4 @@
package net.corda.client.model
package net.corda.client.jfx.model
import javafx.beans.property.ObjectProperty
import javafx.beans.value.ObservableValue

View File

@ -1,13 +1,13 @@
package net.corda.client.model
package net.corda.client.jfx.model
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import net.corda.client.fxutils.firstOrDefault
import net.corda.client.fxutils.firstOrNullObservable
import net.corda.client.fxutils.fold
import net.corda.client.fxutils.map
import net.corda.client.jfx.utils.firstOrDefault
import net.corda.client.jfx.utils.firstOrNullObservable
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
import net.corda.core.crypto.CompositeKey
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache.MapChange
@ -48,4 +48,4 @@ class NetworkIdentityModel {
fun lookup(publicKey: PublicKey): ObservableValue<NodeInfo?> = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey.keys.any { it == publicKey } }) {
it.legalIdentity.owningKey.keys.any { it == publicKey }
}
}
}

View File

@ -1,4 +1,4 @@
package net.corda.client.model
package net.corda.client.jfx.model
import com.google.common.net.HostAndPort
import javafx.beans.property.SimpleObjectProperty
@ -11,7 +11,6 @@ import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.seconds
import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.config.SSLConfiguration
import net.corda.node.services.messaging.CordaRPCClient
import rx.Observable
import rx.subjects.PublishSubject
@ -52,8 +51,8 @@ class NodeMonitorModel {
* Register for updates to/from a given vault.
* TODO provide an unsubscribe mechanism
*/
fun register(nodeHostAndPort: HostAndPort, sslConfig: SSLConfiguration, username: String, password: String) {
val client = CordaRPCClient(nodeHostAndPort, sslConfig){
fun register(nodeHostAndPort: HostAndPort, username: String, password: String) {
val client = CordaRPCClient(nodeHostAndPort){
maxRetryInterval = 10.seconds.toMillis()
}
client.start(username, password)
@ -97,4 +96,4 @@ class NodeMonitorModel {
proxyObservable.set(proxy)
}
}
}

View File

@ -1,11 +1,11 @@
package net.corda.client.model
package net.corda.client.jfx.model
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import net.corda.client.fxutils.*
import net.corda.client.jfx.utils.*
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.ListChangeListener

View File

@ -1,10 +1,10 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.beans.binding.Bindings
import javafx.beans.value.ObservableValue
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.stream
import net.corda.client.model.ExchangeRate
import net.corda.client.jfx.model.ExchangeRate
import net.corda.core.contracts.Amount
import org.fxmisc.easybind.EasyBind
import java.util.*

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.beans.Observable
import javafx.beans.value.ObservableValue

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import co.paralleluniverse.common.util.VisibleForTesting
import javafx.collections.ListChangeListener

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.MapChangeListener

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.application.Platform
import javafx.beans.property.SimpleObjectProperty

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.beans.binding.Bindings
import javafx.beans.binding.BooleanBinding
@ -306,4 +306,4 @@ fun <A> ObservableList<A>.firstOrDefault(default: ObservableValue<A?>, predicate
*/
fun <A> ObservableList<A>.firstOrNullObservable(predicate: (A) -> Boolean): ObservableValue<A?> {
return Bindings.createObjectBinding({ this.firstOrNull(predicate) }, arrayOf(this))
}
}

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import com.sun.javafx.collections.MapListenerHelper
import javafx.beans.InvalidationListener

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.ObservableList

View File

@ -1,13 +1,14 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class ReplayedListTest {
var sourceList = FXCollections.observableArrayList(1234)
var sourceList: ObservableList<Int> = FXCollections.observableArrayList(1234)
var replayedList = ReplayedList(sourceList)
@Before

View File

@ -1,4 +1,4 @@
package net.corda.client.fxutils
package net.corda.client.jfx.utils
import javafx.collections.MapChangeListener
import javafx.collections.ObservableMap

44
client/mock/build.gradle Normal file
View File

@ -0,0 +1,44 @@
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
description 'Corda client mock modules'
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
//noinspection GroovyAssignabilityCheck
configurations {
// we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment
runtime.exclude module: 'isolated'
}
sourceSets {
test {
resources {
srcDir "../../config/test"
}
}
}
// To find potential version conflicts, run "gradle htmlDependencyReport" and then look in
// build/reports/project/dependencies/index.html for green highlighted parts of the tree.
dependencies {
compile project(":node")
// Unit testing helpers.
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
testCompile project(':test-utils')
}

View File

@ -73,11 +73,9 @@ class EventGenerator(
}
val moveCashGenerator =
amountIssuedGenerator.combine(
partyGenerator
) { amountIssued, recipient ->
amountIssuedGenerator.combine(partyGenerator) { amountIssued, recipient ->
CashFlowCommand.PayCash(
amount = amountIssued,
amount = amountIssued.withoutIssuer(),
recipient = recipient
)
}
@ -101,4 +99,4 @@ class EventGenerator(
val bankOfCordaIssueGenerator = Generator.frequency(
0.6 to issueCashGenerator
)
}
}

View File

@ -31,42 +31,42 @@ import java.util.*
*
* The above will generate a random list of animals.
*/
class Generator<out A : Any>(val generate: (SplittableRandom) -> ErrorOr<A>) {
class Generator<out A>(val generate: (SplittableRandom) -> ErrorOr<A>) {
// Functor
fun <B : Any> map(function: (A) -> B): Generator<B> =
fun <B> map(function: (A) -> B): Generator<B> =
Generator { generate(it).map(function) }
// Applicative
fun <B : Any> product(other: Generator<(A) -> B>) =
fun <B> product(other: Generator<(A) -> B>) =
Generator { generate(it).combine(other.generate(it)) { a, f -> f(a) } }
fun <B : Any, R : Any> combine(other1: Generator<B>, function: (A, B) -> R) =
fun <B, R> combine(other1: Generator<B>, function: (A, B) -> R) =
product<R>(other1.product(pure({ b -> { a -> function(a, b) } })))
fun <B : Any, C : Any, R : Any> combine(other1: Generator<B>, other2: Generator<C>, function: (A, B, C) -> R) =
fun <B, C, R> combine(other1: Generator<B>, other2: Generator<C>, function: (A, B, C) -> R) =
product<R>(other1.product(other2.product(pure({ c -> { b -> { a -> function(a, b, c) } } }))))
fun <B : Any, C : Any, D : Any, R : Any> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, function: (A, B, C, D) -> R) =
fun <B, C, D, R> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, function: (A, B, C, D) -> R) =
product<R>(other1.product(other2.product(other3.product(pure({ d -> { c -> { b -> { a -> function(a, b, c, d) } } } })))))
fun <B : Any, C : Any, D : Any, E : Any, R : Any> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, other4: Generator<E>, function: (A, B, C, D, E) -> R) =
fun <B, C, D, E, R> combine(other1: Generator<B>, other2: Generator<C>, other3: Generator<D>, other4: Generator<E>, function: (A, B, C, D, E) -> R) =
product<R>(other1.product(other2.product(other3.product(other4.product(pure({ e -> { d -> { c -> { b -> { a -> function(a, b, c, d, e) } } } } }))))))
// Monad
fun <B : Any> bind(function: (A) -> Generator<B>) =
fun <B> bind(function: (A) -> Generator<B>) =
Generator { generate(it).bind { a -> function(a).generate(it) } }
companion object {
fun <A : Any> pure(value: A) = Generator { ErrorOr(value) }
fun <A : Any> impure(valueClosure: () -> A) = Generator { ErrorOr(valueClosure()) }
fun <A : Any> fail(error: Exception) = Generator<A> { ErrorOr.of(error) }
fun <A> pure(value: A) = Generator { ErrorOr(value) }
fun <A> impure(valueClosure: () -> A) = Generator { ErrorOr(valueClosure()) }
fun <A> fail(error: Exception) = Generator<A> { ErrorOr.of(error) }
// Alternative
fun <A : Any> choice(generators: List<Generator<A>>) = intRange(0, generators.size - 1).bind { generators[it] }
fun <A> choice(generators: List<Generator<A>>) = intRange(0, generators.size - 1).bind { generators[it] }
fun <A : Any> success(generate: (SplittableRandom) -> A) = Generator { ErrorOr(generate(it)) }
fun <A : Any> frequency(generators: List<Pair<Double, Generator<A>>>): Generator<A> {
fun <A> success(generate: (SplittableRandom) -> A) = Generator { ErrorOr(generate(it)) }
fun <A> frequency(generators: List<Pair<Double, Generator<A>>>): Generator<A> {
val ranges = mutableListOf<Pair<Double, Double>>()
var current = 0.0
generators.forEach {
@ -87,7 +87,7 @@ class Generator<out A : Any>(val generate: (SplittableRandom) -> ErrorOr<A>) {
}
}
fun <A : Any> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
fun <A> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
val result = mutableListOf<A>()
for (generator in generators) {
val element = generator.generate(it)
@ -103,9 +103,9 @@ class Generator<out A : Any>(val generate: (SplittableRandom) -> ErrorOr<A>) {
}
}
fun <A : Any> Generator.Companion.frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
fun <A> Generator.Companion.frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
fun <A : Any> Generator<A>.generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
fun <A> Generator<A>.generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
var error: Throwable? = null
for (i in 0..numberOfTries - 1) {
val result = generate(random)
@ -124,6 +124,7 @@ fun <A : Any> Generator<A>.generateOrFail(random: SplittableRandom, numberOfTrie
}
fun Generator.Companion.int() = Generator.success(SplittableRandom::nextInt)
fun Generator.Companion.long() = Generator.success(SplittableRandom::nextLong)
fun Generator.Companion.bytes(size: Int): Generator<ByteArray> = Generator.success { random ->
ByteArray(size) { random.nextInt().toByte() }
}
@ -143,7 +144,7 @@ fun Generator.Companion.doubleRange(from: Double, to: Double): Generator<Double>
from + it.nextDouble() * (to - from)
}
fun <A : Any> Generator.Companion.replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
fun <A> Generator.Companion.replicate(number: Int, generator: Generator<A>): Generator<List<A>> {
val generators = mutableListOf<Generator<A>>()
for (i in 1..number) {
generators.add(generator)
@ -152,7 +153,7 @@ fun <A : Any> Generator.Companion.replicate(number: Int, generator: Generator<A>
}
fun <A : Any> Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator<A>) = Generator<List<A>> {
fun <A> Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator<A>) = Generator<List<A>> {
val chance = (meanSize - 1) / meanSize
val result = mutableListOf<A>()
var finish = false
@ -173,8 +174,8 @@ fun <A : Any> Generator.Companion.replicatePoisson(meanSize: Double, generator:
ErrorOr(result)
}
fun <A : Any> Generator.Companion.pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
fun <A : Any> Generator.Companion.pickN(number: Int, list: List<A>) = Generator<List<A>> {
fun <A> Generator.Companion.pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
fun <A> Generator.Companion.pickN(number: Int, list: List<A>) = Generator<List<A>> {
val mask = BitSet(list.size)
val size = Math.min(list.size, number)
for (i in 0..size - 1) {
@ -199,15 +200,13 @@ fun <A : Any> Generator.Companion.pickN(number: Int, list: List<A>) = Generator<
fun <A> Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) =
sampleBernoulli(listOf(collection), maxRatio)
fun <A> Generator.Companion.sampleBernoulli(collection: Collection<A>, maxRatio: Double = 1.0): Generator<List<A>> =
intRange(0, (maxRatio * collection.size).toInt()).bind { howMany ->
replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
val result = mutableListOf<A>()
collection.forEachIndexed { index, element ->
if (chances[index] < howMany.toDouble() / collection.size.toDouble()) {
result.add(element)
}
fun <A> Generator.Companion.sampleBernoulli(collection: Collection<A>, meanRatio: Double = 1.0): Generator<List<A>> =
replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances ->
val result = mutableListOf<A>()
collection.forEachIndexed { index, element ->
if (chances[index] < meanRatio) {
result.add(element)
}
result
}
result
}

View File

@ -2,11 +2,12 @@ myLegalName : "Bank A"
nearestCity : "London"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "localhost:31337"
webAddress : "localhost:31339"
p2pAddress : "localhost:10002"
rpcAddress : "localhost:10003"
webAddress : "localhost:10004"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:12345"
address : "localhost:10000"
legalName : "Network Map Service"
}
useHTTPS : false

View File

@ -2,11 +2,12 @@ myLegalName : "Bank B"
nearestCity : "London"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "localhost:31338"
webAddress : "localhost:31340"
p2pAddress : "localhost:10005"
rpcAddress : "localhost:10006"
webAddress : "localhost:10007"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:12345"
address : "localhost:10000"
legalName : "Network Map Service"
}
useHTTPS : false

View File

@ -16,6 +16,11 @@
<PatternLayout pattern="%highlight{%level{length=1} %d{HH:mm:ss} [%t] %c{2}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red}" />
</Console>
<!-- Required for printBasicInfo -->
<Console name="Console-Appender-Println" target="SYSTEM_OUT">
<PatternLayout pattern="%msg%n" />
</Console>
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingFile name="RollingFile-Appender"
@ -48,5 +53,9 @@
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
<AppenderRef ref="RollingFile-Appender" />
</Root>
<Logger name="BasicInfo" additivity="false">
<AppenderRef ref="Console-Appender-Println"/>
<AppenderRef ref="RollingFile-Appender" />
</Logger>
</Loggers>
</Configuration>

View File

@ -2,7 +2,7 @@ myLegalName : "Notary Service"
nearestCity : "London"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "localhost:12345"
webAddress : "localhost:12346"
p2pAddress : "localhost:10000"
webAddress : "localhost:10001"
extraAdvertisedServiceIds : [ "corda.notary.validating" ]
useHTTPS : false
useHTTPS : false

View File

@ -7,6 +7,10 @@
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5level] %d{HH:mm:ss.SSS} [%t] %c{2}.%M - %msg%n" />
</Console>
<!-- Required for printBasicInfo -->
<Console name="Console-Appender-Println" target="SYSTEM_OUT">
<PatternLayout pattern="%msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="info">
@ -15,5 +19,8 @@
<Logger name="net.corda" level="${sys:defaultLogLevel}" additivity="false">
<AppenderRef ref="Console-Appender"/>
</Logger>
<Logger name="BasicInfo" additivity="false">
<AppenderRef ref="Console-Appender-Println"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -50,10 +50,13 @@ dependencies {
// Thread safety annotations
compile "com.google.code.findbugs:jsr305:3.0.1"
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
// Log4J: logging framework (ONLY explicitly referenced by net.corda.core.utilities.Logging.kt)
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
// SLF4J: commons-logging bindings for a SLF4J back end
compile "org.slf4j:jcl-over-slf4j:$slf4j_version"
compile "org.slf4j:slf4j-api:$slf4j_version"
// AssertJ: for fluent assertions for testing
testCompile "org.assertj:assertj-core:${assertj_version}"
@ -91,8 +94,14 @@ dependencies {
compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
// RS API: Response type and codes for ApiUtils.
compile "javax.ws.rs:javax.ws.rs-api:2.0"
compile "javax.ws.rs:javax.ws.rs-api:2.0.1"
// Requery: SQL based query & persistence for Kotlin
compile "io.requery:requery-kotlin:$requery_version"
}
configurations.compile {
// We want to use SLF4J's version of these binding: jcl-over-slf4j
// Remove any transitive dependency on Apache's version.
exclude group: 'commons-logging', module: 'commons-logging'
}

View File

@ -357,7 +357,7 @@ data class ErrorOr<out A> private constructor(val value: A?, val error: Throwabl
}
// Monad
fun <B : Any> bind(function: (A) -> ErrorOr<B>): ErrorOr<B> {
fun <B> bind(function: (A) -> ErrorOr<B>): ErrorOr<B> {
return if (error == null) {
function(value as A)
} else {

View File

@ -27,7 +27,9 @@ fun commodity(code: String) = Commodity.getInstance(code)!!
@JvmField val GBP = currency("GBP")
@JvmField val EUR = currency("EUR")
@JvmField val CHF = currency("CHF")
@JvmField val FCOJ = commodity("FCOJ")
@JvmField val JPY = currency("JPY")
@JvmField val RUB = currency("RUB")
@JvmField val FCOJ = commodity("FCOJ") // Frozen concentrated orange juice, yum!
fun DOLLARS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, USD)
fun DOLLARS(amount: Double): Amount<Currency> = Amount((amount * 100).toLong(), USD)

View File

@ -39,7 +39,7 @@ import java.util.*
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
companion object {
/**
* Build an amount from a decimal representation. For example, with an input of "12.34" GBP,
* Build a currency amount from a decimal representation. For example, with an input of "12.34" GBP,
* returns an amount with a quantity of "1234".
*
* @see Amount<Currency>.toDecimal
@ -48,6 +48,66 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
val longQuantity = quantity.movePointRight(currency.defaultFractionDigits).toLong()
return Amount(longQuantity, currency)
}
private val currencySymbols: Map<String, Currency> = mapOf(
"$" to USD,
"£" to GBP,
"" to EUR,
"¥" to JPY,
"" to RUB
)
private val currencyCodes: Map<String, Currency> by lazy { Currency.getAvailableCurrencies().map { it.currencyCode to it }.toMap() }
/**
* Returns an amount that is equal to the given currency amount in text. Examples of what is supported:
*
* - 12 USD
* - 14.50 USD
* - 10 USD
* - 30 CHF
* - $10.24
* - £13
* - 5000
*
* Note this method does NOT respect internationalisation rules: it ignores commas and uses . as the
* decimal point separator, always. It also ignores the users locale:
*
* - $ is always USD,
* - £ is always GBP
* - is always the Euro
* - ¥ is always Japanese Yen.
* - is always the Russian ruble.
*
* Thus an input of $12 expecting some other countries dollar will not work. Do your own parsing if
* you need correct handling of currency amounts with locale-sensitive handling.
*
* @throws IllegalArgumentException if the input string was not understood.
*/
fun parseCurrency(input: String): Amount<Currency> {
val i = input.filter { it != ',' }
try {
// First check the symbols at the front.
for ((symbol, currency) in currencySymbols) {
if (i.startsWith(symbol)) {
val rest = i.substring(symbol.length)
return fromDecimal(BigDecimal(rest), currency)
}
}
// Now check the codes at the end.
val split = i.split(' ')
if (split.size == 2) {
val (rest, code) = split
for ((cc, currency) in currencyCodes) {
if (cc == code) {
return fromDecimal(BigDecimal(rest), currency)
}
}
}
} catch(e: Exception) {
throw IllegalArgumentException("Could not parse $input as a currency", e)
}
throw IllegalArgumentException("Did not recognise the currency in $input or could not parse")
}
}
init {
@ -65,17 +125,17 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
constructor(quantity: BigInteger, token: T) : this(quantity.toLong(), token)
operator fun plus(other: Amount<T>): Amount<T> {
checkCurrency(other)
checkToken(other)
return Amount(Math.addExact(quantity, other.quantity), token)
}
operator fun minus(other: Amount<T>): Amount<T> {
checkCurrency(other)
checkToken(other)
return Amount(Math.subtractExact(quantity, other.quantity), token)
}
private fun checkCurrency(other: Amount<T>) {
require(other.token == token) { "Currency mismatch: ${other.token} vs $token" }
private fun checkToken(other: Amount<T>) {
require(other.token == token) { "Token mismatch: ${other.token} vs $token" }
}
operator fun div(other: Long): Amount<T> = Amount(quantity / other, token)
@ -83,10 +143,16 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
operator fun div(other: Int): Amount<T> = Amount(quantity / other, token)
operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), token)
override fun toString(): String = (BigDecimal(quantity).divide(BigDecimal(100))).setScale(2).toPlainString() + " " + token
override fun toString(): String {
val bd = if (token is Currency)
BigDecimal(quantity).movePointLeft(token.defaultFractionDigits)
else
BigDecimal(quantity)
return bd.toPlainString() + " " + token
}
override fun compareTo(other: Amount<T>): Int {
checkCurrency(other)
checkToken(other)
return quantity.compareTo(other.quantity)
}
}

View File

@ -3,9 +3,7 @@ package net.corda.core.contracts
import net.corda.core.crypto.CompositeKey
import net.corda.core.flows.FlowException
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException() {
override fun toString() = "Insufficient balance, missing $amountMissing"
}
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing")
/**
* Interface for contract states representing assets which are fungible, countable and issued by a

View File

@ -10,18 +10,20 @@ import kotlin.reflect.KClass
interface PluginServiceHub : ServiceHub {
/**
* Register the flow factory we wish to use when a initiating party attempts to communicate with us. The
* registration is done against a marker [KClass] which is sent in the session handshake by the other party. If this
* registration is done against a marker [Class] which is sent in the session handshake by the other party. If this
* marker class has been registered then the corresponding factory will be used to create the flow which will
* communicate with the other side. If there is no mapping then the session attempt is rejected.
* @param markerClass The marker [KClass] present in a session initiation attempt, which is a 1:1 mapping to a [Class]
* using the <pre>::class</pre> construct. Conventionally this is a [FlowLogic] subclass, however any class can
* be used, with the default being the class of the initiating flow. This enables the registration to be of the
* form: registerFlowInitiator(InitiatorFlow::class, ::InitiatedFlow)
* @param markerClass The marker [Class] present in a session initiation attempt. Conventionally this is a [FlowLogic]
* subclass, however any class can be used, with the default being the class of the initiating flow. This enables
* the registration to be of the form: `registerFlowInitiator(InitiatorFlow.class, InitiatedFlow::new)`
* @param flowFactory The flow factory generating the initiated flow.
*/
fun registerFlowInitiator(markerClass: Class<*>, flowFactory: (Party) -> FlowLogic<*>)
// TODO: remove dependency on Kotlin relfection (Kotlin KClass -> Java Class).
fun registerFlowInitiator(markerClass: KClass<*>, flowFactory: (Party) -> FlowLogic<*>)
@Deprecated(message = "Use overloaded method which uses Class instead of KClass. This is scheduled for removal in a future release.")
fun registerFlowInitiator(markerClass: KClass<*>, flowFactory: (Party) -> FlowLogic<*>) {
registerFlowInitiator(markerClass.java, flowFactory)
}
/**
* Return the flow factory that has been registered with [markerClass], or null if no factory is found.

View File

@ -203,15 +203,15 @@ interface VaultService {
onlyFromParties: Set<AbstractParty>? = null): Pair<TransactionBuilder, List<CompositeKey>>
/**
* Return [ContractState]s of a given [Contract] type and list of [Vault.StateStatus]
* Return [ContractState]s of a given [Contract] type and [Iterable] of [Vault.StateStatus].
*/
fun <T : ContractState> states(clazzes: Set<Class<T>>, statuses: EnumSet<Vault.StateStatus>): List<StateAndRef<T>>
fun <T : ContractState> states(clazzes: Set<Class<T>>, statuses: EnumSet<Vault.StateStatus>): Iterable<StateAndRef<T>>
}
inline fun <reified T: ContractState> VaultService.unconsumedStates(): List<StateAndRef<T>> =
inline fun <reified T: ContractState> VaultService.unconsumedStates(): Iterable<StateAndRef<T>> =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
inline fun <reified T: ContractState> VaultService.consumedStates(): List<StateAndRef<T>> =
inline fun <reified T: ContractState> VaultService.consumedStates(): Iterable<StateAndRef<T>> =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.CONSUMED))
/** Returns the [linearState] heads only when the type of the state would be considered an 'instanceof' the given type. */

View File

@ -92,6 +92,24 @@ class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver()
return type.interfaces.any { it.isAnnotationPresent(CordaSerializable::class.java) || hasAnnotationOnInterface(it) }
|| (type.superclass != null && hasAnnotationOnInterface(type.superclass))
}
// Need to clear out class names from attachments.
override fun reset() {
super.reset()
// Kryo creates a cache of class name to Class<*> which does not work so well with multiple class loaders.
// TODO: come up with a more efficient way. e.g. segregate the name space by class loader.
if(nameToClass != null) {
val classesToRemove: MutableList<String> = ArrayList(nameToClass.size)
for (entry in nameToClass.entries()) {
if (entry.value.classLoader is AttachmentsClassLoader) {
classesToRemove += entry.key
}
}
for (className in classesToRemove) {
nameToClass.remove(className)
}
}
}
}
interface ClassWhitelist {

View File

@ -17,6 +17,7 @@ import net.corda.core.utilities.NonEmptySetSerializer
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.objenesis.strategy.StdInstantiatorStrategy
import org.slf4j.Logger
import java.io.BufferedInputStream
import java.util.*
@ -34,7 +35,7 @@ object DefaultKryoCustomizer {
// for change to a class.
setDefaultSerializer(CompatibleFieldSerializer::class.java)
// Take the safest route here and allow subclasses to have fields named the same as super classes.
fieldSerializerConfig.setCachedFieldNameStrategy(FieldSerializer.CachedFieldNameStrategy.EXTENDED)
fieldSerializerConfig.cachedFieldNameStrategy = FieldSerializer.CachedFieldNameStrategy.EXTENDED
// Allow construction of objects using a JVM backdoor that skips invoking the constructors, if there is no
// no-arg constructor available.
@ -78,8 +79,10 @@ object DefaultKryoCustomizer {
register(MetaData::class.java, MetaDataSerializer)
register(BitSet::class.java, ReferencesAwareJavaSerializer)
addDefaultSerializer(Logger::class.java, LoggerSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
}
}
}
}

View File

@ -3,9 +3,10 @@ package net.corda.core.serialization
import com.esotericsoftware.kryo.*
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.pool.KryoPool
import com.esotericsoftware.kryo.serializers.JavaSerializer
import com.esotericsoftware.kryo.serializers.MapSerializer
import com.esotericsoftware.kryo.util.MapReferenceResolver
import com.google.common.annotations.VisibleForTesting
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.node.AttachmentsClassLoader
@ -15,6 +16,8 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.*
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
@ -60,12 +63,9 @@ import kotlin.reflect.jvm.javaType
*/
// A convenient instance of Kryo pre-configured with some useful things. Used as a default by various functions.
private val THREAD_LOCAL_KRYO: ThreadLocal<Kryo> = ThreadLocal.withInitial { createKryo() }
fun p2PKryo(): KryoPool = kryoPool
// Same again, but this has whitelisting turned off for internal storage use only.
private val INTERNAL_THREAD_LOCAL_KRYO: ThreadLocal<Kryo> = ThreadLocal.withInitial { createInternalKryo() }
fun threadLocalP2PKryo(): Kryo = THREAD_LOCAL_KRYO.get()
fun threadLocalStorageKryo(): Kryo = INTERNAL_THREAD_LOCAL_KRYO.get()
fun storageKryo(): KryoPool = internalKryoPool
/**
* A type safe wrapper around a byte array that contains a serialised object. You can call [SerializedBytes.deserialize]
@ -82,26 +82,34 @@ class SerializedBytes<T : Any>(bytes: ByteArray, val internalOnly: Boolean = fal
private val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray())
// Some extension functions that make deserialisation convenient and provide auto-casting of the result.
fun <T : Any> ByteArray.deserialize(kryo: Kryo = threadLocalP2PKryo()): T {
fun <T : Any> ByteArray.deserialize(kryo: KryoPool = p2PKryo()): T {
Input(this).use {
val header = OpaqueBytes(it.readBytes(8))
if (header != KryoHeaderV0_1) {
throw KryoException("Serialized bytes header does not match any known format.")
}
@Suppress("UNCHECKED_CAST")
return kryo.readClassAndObject(it) as T
return kryo.run { k -> k.readClassAndObject(it) as T }
}
}
fun <T : Any> OpaqueBytes.deserialize(kryo: Kryo = threadLocalP2PKryo()): T {
// TODO: The preferred usage is with a pool. Try and eliminate use of this from RPC.
fun <T : Any> ByteArray.deserialize(kryo: Kryo): T = deserialize(kryo.asPool())
fun <T : Any> OpaqueBytes.deserialize(kryo: KryoPool = p2PKryo()): T {
return this.bytes.deserialize(kryo)
}
// The more specific deserialize version results in the bytes being cached, which is faster.
@JvmName("SerializedBytesWireTransaction")
fun SerializedBytes<WireTransaction>.deserialize(kryo: Kryo = threadLocalP2PKryo()): WireTransaction = WireTransaction.deserialize(this, kryo)
fun SerializedBytes<WireTransaction>.deserialize(kryo: KryoPool = p2PKryo()): WireTransaction = WireTransaction.deserialize(this, kryo)
fun <T : Any> SerializedBytes<T>.deserialize(kryo: Kryo = if (internalOnly) threadLocalStorageKryo() else threadLocalP2PKryo()): T = bytes.deserialize(kryo)
fun <T : Any> SerializedBytes<T>.deserialize(kryo: KryoPool = if (internalOnly) storageKryo() else p2PKryo()): T = bytes.deserialize(kryo)
fun <T : Any> SerializedBytes<T>.deserialize(kryo: Kryo): T = bytes.deserialize(kryo.asPool())
// Internal adapter for use when we haven't yet converted to a pool, or for tests.
private fun Kryo.asPool(): KryoPool = (KryoPool.Builder { this }.build())
/**
* A serialiser that avoids writing the wrapper class to the byte stream, thus ensuring [SerializedBytes] is a pure
@ -122,7 +130,11 @@ object SerializedBytesSerializer : Serializer<SerializedBytes<Any>>() {
* Can be called on any object to convert it to a byte array (wrapped by [SerializedBytes]), regardless of whether
* the type is marked as serializable or was designed for it (so be careful!).
*/
fun <T : Any> T.serialize(kryo: Kryo = threadLocalP2PKryo(), internalOnly: Boolean = false): SerializedBytes<T> {
fun <T : Any> T.serialize(kryo: KryoPool = p2PKryo(), internalOnly: Boolean = false): SerializedBytes<T> {
return kryo.run { k -> serialize(k, internalOnly) }
}
fun <T : Any> T.serialize(kryo: Kryo, internalOnly: Boolean = false): SerializedBytes<T> {
val stream = ByteArrayOutputStream()
Output(stream).use {
it.writeBytes(KryoHeaderV0_1.bytes)
@ -399,14 +411,12 @@ object KotlinObjectSerializer : Serializer<DeserializeAsKotlinObjectDef>() {
}
// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors.
fun createInternalKryo(k: Kryo = CordaKryo(makeNoWhitelistClassResolver())): Kryo {
return DefaultKryoCustomizer.customize(k)
}
private val internalKryoPool = KryoPool.Builder { DefaultKryoCustomizer.customize(CordaKryo(makeNoWhitelistClassResolver())) }.build()
private val kryoPool = KryoPool.Builder { DefaultKryoCustomizer.customize(CordaKryo(makeStandardClassResolver())) }.build()
// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors.
fun createKryo(k: Kryo = CordaKryo(makeStandardClassResolver())): Kryo {
return DefaultKryoCustomizer.customize(k)
}
@VisibleForTesting
fun createTestKryo(): Kryo = DefaultKryoCustomizer.customize(CordaKryo(makeNoWhitelistClassResolver()))
/**
* We need to disable whitelist checking during calls from our Kryo code to register a serializer, since it checks
@ -475,21 +485,20 @@ inline fun <reified T : Any> Kryo.noReferencesWithin() {
class NoReferencesSerializer<T>(val baseSerializer: Serializer<T>) : Serializer<T>() {
override fun read(kryo: Kryo, input: Input, type: Class<T>): T {
val previousValue = kryo.setReferences(false)
try {
return baseSerializer.read(kryo, input, type)
} finally {
kryo.references = previousValue
}
return kryo.withoutReferences { baseSerializer.read(kryo, input, type) }
}
override fun write(kryo: Kryo, output: Output, obj: T) {
val previousValue = kryo.setReferences(false)
try {
baseSerializer.write(kryo, output, obj)
} finally {
kryo.references = previousValue
}
kryo.withoutReferences { baseSerializer.write(kryo, output, obj) }
}
}
fun <T> Kryo.withoutReferences(block: () -> T): T {
val previousValue = setReferences(false)
try {
return block()
} finally {
references = previousValue
}
}
@ -518,20 +527,16 @@ object ReferencesAwareJavaSerializer : JavaSerializer() {
val ATTACHMENT_STORAGE = "ATTACHMENT_STORAGE"
var Kryo.attachmentStorage: AttachmentStorage?
val Kryo.attachmentStorage: AttachmentStorage?
get() = this.context.get(ATTACHMENT_STORAGE, null) as AttachmentStorage?
set(value) {
this.context.put(ATTACHMENT_STORAGE, value)
}
//TODO: It's a little workaround for serialization of HashMaps inside contract states.
//Used in Merkle tree calculation. It doesn't cover all the cases of unstable serialization format.
fun extendKryoHash(kryo: Kryo): Kryo {
return kryo.apply {
references = false
register(LinkedHashMap::class.java, MapSerializer())
register(HashMap::class.java, OrderedSerializer)
fun <T> Kryo.withAttachmentStorage(attachmentStorage: AttachmentStorage?, block: () -> T): T {
val priorAttachmentStorage = this.attachmentStorage
this.context.put(ATTACHMENT_STORAGE, attachmentStorage)
try {
return block()
} finally {
this.context.put(ATTACHMENT_STORAGE, priorAttachmentStorage)
}
}
@ -582,3 +587,15 @@ object MetaDataSerializer : Serializer<MetaData>() {
return MetaData(schemeCodeName, versionID, signatureType, timestamp, visibleInputs, signedInputs, merkleRoot, publicKey)
}
}
/** For serialising a Logger. */
@ThreadSafe
object LoggerSerializer : Serializer<Logger>() {
override fun write(kryo: Kryo, output: Output, obj: Logger) {
output.writeString(obj.name)
}
override fun read(kryo: Kryo, input: Input, type: Class<Logger>): Logger {
return LoggerFactory.getLogger(input.readString())
}
}

View File

@ -5,7 +5,7 @@ import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import java.util.*
import com.esotericsoftware.kryo.pool.KryoPool
/**
* The interfaces and classes in this file allow large, singleton style classes to
@ -36,8 +36,6 @@ interface SerializationToken {
/**
* A Kryo serializer for [SerializeAsToken] implementations.
*
* This is registered in [createKryo].
*/
class SerializeAsTokenSerializer<T : SerializeAsToken> : Serializer<T>() {
override fun write(kryo: Kryo, output: Output, obj: T) {
@ -76,8 +74,8 @@ class SerializeAsTokenSerializer<T : SerializeAsToken> : Serializer<T>() {
* Then it is a case of using the companion object methods on [SerializeAsTokenSerializer] to set and clear context as necessary
* on the Kryo instance when serializing to enable/disable tokenization.
*/
class SerializeAsTokenContext(toBeTokenized: Any, kryo: Kryo = createKryo()) {
internal val tokenToTokenized = HashMap<SerializationToken, SerializeAsToken>()
class SerializeAsTokenContext(toBeTokenized: Any, kryoPool: KryoPool) {
internal val tokenToTokenized = mutableMapOf<SerializationToken, SerializeAsToken>()
internal var readOnly = false
init {
@ -90,9 +88,11 @@ class SerializeAsTokenContext(toBeTokenized: Any, kryo: Kryo = createKryo()) {
* accidental registrations from occuring as these could not be deserialized in a deserialization-first
* scenario if they are not part of this iniital context construction serialization.
*/
SerializeAsTokenSerializer.setContext(kryo, this)
toBeTokenized.serialize(kryo)
SerializeAsTokenSerializer.clearContext(kryo)
kryoPool.run { kryo ->
SerializeAsTokenSerializer.setContext(kryo, this)
toBeTokenized.serialize(kryo)
SerializeAsTokenSerializer.clearContext(kryo)
}
readOnly = true
}
}

View File

@ -3,13 +3,12 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.createKryo
import net.corda.core.serialization.extendKryoHash
import net.corda.core.serialization.p2PKryo
import net.corda.core.serialization.serialize
import net.corda.core.serialization.withoutReferences
fun <T : Any> serializedHash(x: T): SecureHash {
val kryo = extendKryoHash(createKryo()) // Dealing with HashMaps inside states.
return x.serialize(kryo).hash
return p2PKryo().run { kryo -> kryo.withoutReferences { x.serialize(kryo).hash } }
}
/**

View File

@ -1,6 +1,6 @@
package net.corda.core.transactions
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.pool.KryoPool
import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.MerkleTree
@ -10,8 +10,8 @@ import net.corda.core.indexOfOrThrow
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.p2PKryo
import net.corda.core.serialization.serialize
import net.corda.core.serialization.threadLocalP2PKryo
import net.corda.core.utilities.Emoji
import java.security.PublicKey
@ -45,7 +45,7 @@ class WireTransaction(
override val id: SecureHash by lazy { merkleTree.hash }
companion object {
fun deserialize(data: SerializedBytes<WireTransaction>, kryo: Kryo = threadLocalP2PKryo()): WireTransaction {
fun deserialize(data: SerializedBytes<WireTransaction>, kryo: KryoPool = p2PKryo()): WireTransaction {
val wtx = data.bytes.deserialize<WireTransaction>(kryo)
wtx.cachedBytes = data
return wtx

View File

@ -7,7 +7,9 @@ import net.corda.core.codePointsString
*/
object Emoji {
// Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default.
val hasEmojiTerminal by lazy { listOf("Apple_Terminal", "iTerm.app").contains(System.getenv("TERM_PROGRAM")) }
val hasEmojiTerminal by lazy {
System.getenv("CORDA_FORCE_EMOJI") != null || System.getenv("TERM_PROGRAM") in listOf("Apple_Terminal", "iTerm.app")
}
@JvmStatic val CODE_SANTA_CLAUS: String = codePointsString(0x1F385)
@JvmStatic val CODE_DIAMOND: String = codePointsString(0x1F537)
@ -32,14 +34,21 @@ object Emoji {
val diamond: String get() = if (emojiMode.get() != null) "$CODE_DIAMOND " else ""
val bagOfCash: String get() = if (emojiMode.get() != null) "$CODE_BAG_OF_CASH " else ""
val newspaper: String get() = if (emojiMode.get() != null) "$CODE_NEWSPAPER " else ""
val rightArrow: String get() = if (emojiMode.get() != null) "$CODE_RIGHT_ARROW " else ""
val leftArrow: String get() = if (emojiMode.get() != null) "$CODE_LEFT_ARROW " else ""
val paperclip: String get() = if (emojiMode.get() != null) "$CODE_PAPERCLIP " else ""
val coolGuy: String get() = if (emojiMode.get() != null) "$CODE_COOL_GUY " else ""
val books: String get() = if (emojiMode.get() != null) "$CODE_BOOKS " else ""
// These have old/non-emoji symbols with better platform support.
val greenTick: String get() = if (emojiMode.get() != null) "$CODE_GREEN_TICK " else ""
val rightArrow: String get() = if (emojiMode.get() != null) "$CODE_RIGHT_ARROW " else "▶︎"
val skullAndCrossbones: String get() = if (emojiMode.get() != null) "$CODE_SKULL_AND_CROSSBONES " else ""
val noEntry: String get() = if (emojiMode.get() != null) "$CODE_NO_ENTRY " else ""
inline fun <T> renderIfSupported(body: () -> T): T {
emojiMode.set(this) // Could be any object.
if (hasEmojiTerminal)
emojiMode.set(this) // Could be any object.
try {
return body()
} finally {

View File

@ -3,7 +3,6 @@ package net.corda.core.utilities
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.core.appender.ConsoleAppender
import org.apache.logging.log4j.core.config.LoggerConfig
import org.slf4j.LoggerFactory
import kotlin.reflect.KClass
@ -56,10 +55,7 @@ object LogHelper {
val loggerContext = LogManager.getContext(false) as LoggerContext
val config = loggerContext.configuration
val loggerConfig = LoggerConfig(name, level, false)
val appender = config.appenders.map { it.value as? ConsoleAppender }.singleOrNull()
appender?.let {
loggerConfig.addAppender(appender, null, null)
}
loggerConfig.addAppender(config.appenders["Console-Appender"], null, null)
config.removeLogger(name)
config.addLogger(name, loggerConfig)
loggerContext.updateLoggers(config)

View File

@ -57,6 +57,7 @@ class ProgressTracker(vararg steps: Step) {
open fun childProgressTracker(): ProgressTracker? = null
}
// TODO: There's no actual way to create these steps anymore!
/** This class makes it easier to relabel a step on the fly, to provide transient information. */
open inner class RelabelableStep(currentLabel: String) : Step(currentLabel) {
override val changes: BehaviorSubject<Change> = BehaviorSubject.create()
@ -88,7 +89,7 @@ class ProgressTracker(vararg steps: Step) {
@CordaSerializable
private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?)
private val childProgressTrackers = HashMap<Step, Child>()
private val childProgressTrackers = mutableMapOf<Step, Child>()
init {
steps.forEach {

View File

@ -1,5 +1,6 @@
package net.corda.core.utilities
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowException
/**
@ -18,6 +19,7 @@ class UntrustworthyData<out T>(private val fromUntrustedWorld: T) {
@Deprecated("Accessing the untrustworthy data directly without validating it first is a bad idea")
get() = fromUntrustedWorld
@Suspendable
@Throws(FlowException::class)
fun <R> unwrap(validator: Validator<T, R>) = validator.validate(fromUntrustedWorld)
@ -26,6 +28,7 @@ class UntrustworthyData<out T>(private val fromUntrustedWorld: T) {
inline fun <R> validate(validator: (T) -> R) = validator(data)
interface Validator<in T, out R> {
@Suspendable
@Throws(FlowException::class)
fun validate(data: T): R
}

View File

@ -0,0 +1,74 @@
package net.corda.core.flows;
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.crypto.Party;
import net.corda.testing.node.MockNetwork;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.Future;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class FlowsInJavaTest {
private final MockNetwork net = new MockNetwork();
private MockNetwork.MockNode node1;
private MockNetwork.MockNode node2;
@Before
public void setUp() {
MockNetwork.BasketOfNodes someNodes = net.createSomeNodes(2);
node1 = someNodes.getPartyNodes().get(0);
node2 = someNodes.getPartyNodes().get(1);
net.runNetwork();
}
@After
public void cleanUp() {
net.stopNodes();
}
@Test
public void suspendableActionInsideUnwrap() throws Exception {
node2.getServices().registerFlowInitiator(SendInUnwrapFlow.class, (otherParty) -> new OtherFlow(otherParty, "Hello"));
Future<String> result = node1.getServices().startFlow(new SendInUnwrapFlow(node2.getInfo().getLegalIdentity())).getResultFuture();
net.runNetwork();
assertThat(result.get()).isEqualTo("Hello");
}
private static class SendInUnwrapFlow extends FlowLogic<String> {
private final Party otherParty;
private SendInUnwrapFlow(Party otherParty) {
this.otherParty = otherParty;
}
@Suspendable
@Override
public String call() throws FlowException {
return receive(String.class, otherParty).unwrap(data -> {
send(otherParty, "Something");
return data;
});
}
}
private static class OtherFlow extends FlowLogic<String> {
private final Party otherParty;
private final String payload;
private OtherFlow(Party otherParty, String payload) {
this.otherParty = otherParty;
this.payload = payload;
}
@Suspendable
@Override
public String call() throws FlowException {
return sendAndReceive(String.class, otherParty, payload).unwrap(data -> data);
}
}
}

View File

@ -23,4 +23,20 @@ class AmountTests {
assertEquals(expected, amount.toDecimal())
assertEquals(amount, Amount.fromDecimal(amount.toDecimal(), amount.token))
}
@Test
fun parsing() {
assertEquals(Amount(1234L, GBP), Amount.parseCurrency("£12.34"))
assertEquals(Amount(1200L, GBP), Amount.parseCurrency("£12"))
assertEquals(Amount(1000L, USD), Amount.parseCurrency("$10"))
assertEquals(Amount(5000L, JPY), Amount.parseCurrency("¥5000"))
assertEquals(Amount(500000L, RUB), Amount.parseCurrency("₽5000"))
assertEquals(Amount(1500000000L, CHF), Amount.parseCurrency("15,000,000 CHF"))
}
@Test
fun rendering() {
assertEquals("5000 JPY", Amount.parseCurrency("¥5000").toString())
assertEquals("50.12 USD", Amount.parseCurrency("$50.12").toString())
}
}

View File

@ -45,22 +45,22 @@ class CryptoUtilsTest {
val sphincsKeyPair = Crypto.generateKeyPair("SPHINCS-256_SHA512")
// not null private keys
assertNotNull(rsaKeyPair.private);
assertNotNull(ecdsaKKeyPair.private);
assertNotNull(ecdsaRKeyPair.private);
assertNotNull(eddsaKeyPair.private);
assertNotNull(sphincsKeyPair.private);
assertNotNull(rsaKeyPair.private)
assertNotNull(ecdsaKKeyPair.private)
assertNotNull(ecdsaRKeyPair.private)
assertNotNull(eddsaKeyPair.private)
assertNotNull(sphincsKeyPair.private)
// not null public keys
assertNotNull(rsaKeyPair.public);
assertNotNull(ecdsaKKeyPair.public);
assertNotNull(ecdsaRKeyPair.public);
assertNotNull(eddsaKeyPair.public);
assertNotNull(sphincsKeyPair.public);
assertNotNull(rsaKeyPair.public)
assertNotNull(ecdsaKKeyPair.public)
assertNotNull(ecdsaRKeyPair.public)
assertNotNull(eddsaKeyPair.public)
assertNotNull(sphincsKeyPair.public)
// fail on unsupported algorithm
try {
val wrongKeyPair = Crypto.generateKeyPair("WRONG_ALG")
Crypto.generateKeyPair("WRONG_ALG")
fail()
} catch (e: Exception) {
// expected
@ -117,15 +117,15 @@ class CryptoUtilsTest {
// test on malformed signatures (even if they change for 1 bit)
for (i in 0..signedData.size - 1) {
val b = signedData.get(i)
signedData.set(i,b.inc())
val b = signedData[i]
signedData[i] = b.inc()
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
signedData.set(i,b.dec())
signedData[i] = b.dec()
}
}
@ -176,7 +176,7 @@ class CryptoUtilsTest {
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0,signedData[0].inc())
signedData[0] = signedData[0].inc()
try {
keyPair.verify(signedData, testBytes)
fail()
@ -232,7 +232,7 @@ class CryptoUtilsTest {
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
signedData[0] = signedData[0].inc()
try {
keyPair.verify(signedData, testBytes)
fail()
@ -288,7 +288,7 @@ class CryptoUtilsTest {
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
signedData[0] = signedData[0].inc()
try {
keyPair.verify(signedData, testBytes)
fail()
@ -344,7 +344,7 @@ class CryptoUtilsTest {
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
signedData[0] = signedData[0].inc()
try {
keyPair.verify(signedData, testBytes)
fail()
@ -357,7 +357,7 @@ class CryptoUtilsTest {
@Test
fun `Check supported algorithms`() {
val algList : List<String> = Crypto.listSupportedSignatureSchemes()
val expectedAlgSet = setOf<String>("RSA_SHA256","ECDSA_SECP256K1_SHA256", "ECDSA_SECP256R1_SHA256", "EDDSA_ED25519_SHA512","SPHINCS-256_SHA512")
val expectedAlgSet = setOf("RSA_SHA256","ECDSA_SECP256K1_SHA256", "ECDSA_SECP256R1_SHA256", "EDDSA_ED25519_SHA512","SPHINCS-256_SHA512")
assertTrue { Sets.symmetricDifference(expectedAlgSet,algList.toSet()).isEmpty(); }
}
@ -601,12 +601,12 @@ class CryptoUtilsTest {
@Test
fun `Failure test between K1 and R1 keys`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val privK1= keyPairK1.private
val encodedPrivK1 = privK1.encoded
val decodedPrivK1 = Crypto.decodePrivateKey(encodedPrivK1)
val keyPairR1 = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privR1, pubR1) = keyPairR1
val privR1 = keyPairR1.private
val encodedPrivR1 = privR1.encoded
val decodedPrivR1 = Crypto.decodePrivateKey(encodedPrivR1)
@ -616,7 +616,7 @@ class CryptoUtilsTest {
@Test
fun `Decoding Failure on randomdata as key`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val privK1 = keyPairK1.private
val encodedPrivK1 = privK1.encoded
// Test on random encoded bytes.
@ -626,7 +626,7 @@ class CryptoUtilsTest {
// fail on fake key.
try {
val decodedFake = Crypto.decodePrivateKey(fakeEncodedKey)
Crypto.decodePrivateKey(fakeEncodedKey)
fail()
} catch (e: Exception) {
// expected
@ -636,21 +636,20 @@ class CryptoUtilsTest {
@Test
fun `Decoding Failure on malformed keys`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val privK1 = keyPairK1.private
val encodedPrivK1 = privK1.encoded
// fail on malformed key.
for (i in 0..encodedPrivK1.size - 1) {
val b = encodedPrivK1.get(i)
encodedPrivK1.set(i,b.inc())
val b = encodedPrivK1[i]
encodedPrivK1[i] = b.inc()
try {
val decodedFake = Crypto.decodePrivateKey(encodedPrivK1)
println("OK")
Crypto.decodePrivateKey(encodedPrivK1)
fail()
} catch (e: Exception) {
// expected
}
encodedPrivK1.set(i,b.dec())
encodedPrivK1[i] = b.dec()
}
}
}

View File

@ -1,18 +1,20 @@
package net.corda.core.crypto
import com.esotericsoftware.kryo.serializers.MapSerializer
import com.esotericsoftware.kryo.KryoException
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.*
import net.corda.core.transactions.*
import net.corda.core.utilities.*
import net.corda.core.serialization.p2PKryo
import net.corda.core.serialization.serialize
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_PUBKEY_1
import net.corda.core.utilities.TEST_TX_TIME
import net.corda.testing.MEGA_CORP
import net.corda.testing.MEGA_CORP_PUBKEY
import net.corda.testing.ledger
import org.junit.Test
import java.util.*
import kotlin.test.*
class PartialMerkleTreeTest {
@ -208,15 +210,12 @@ class PartialMerkleTreeTest {
assertFalse(pmt.verify(wrongRoot, inclHashes))
}
@Test
fun `hash map serialization`() {
@Test(expected = KryoException::class)
fun `hash map serialization not allowed`() {
val hm1 = hashMapOf("a" to 1, "b" to 2, "c" to 3, "e" to 4)
assert(serializedHash(hm1) == serializedHash(hm1.serialize().deserialize())) // It internally uses the ordered HashMap extension.
val kryo = extendKryoHash(createKryo())
assertTrue(kryo.getSerializer(HashMap::class.java) is OrderedSerializer)
assertTrue(kryo.getSerializer(LinkedHashMap::class.java) is MapSerializer)
val hm2 = hm1.serialize(kryo).deserialize(kryo)
assert(hm1.hashCode() == hm2.hashCode())
p2PKryo().run { kryo ->
hm1.serialize(kryo)
}
}
private fun makeSimpleCashWtx(notary: Party, timestamp: Timestamp? = null, attachments: List<SecureHash> = emptyList()): WireTransaction {

View File

@ -165,11 +165,11 @@ class ContractUpgradeFlowTest {
// Starts contract upgrade flow.
a.services.startFlow(ContractUpgradeFlow.Instigator(stateAndRef, CashV2::class.java))
mockNet.runNetwork()
// Get contract state form the vault.
val state = databaseTransaction(a.database) { a.vault.unconsumedStates<ContractState>() }
assertTrue(state.single().state.data is CashV2.State, "Contract state is upgraded to the new version.")
assertEquals(Amount(1000000, USD).`issued by`(a.info.legalIdentity.ref(1)), (state.first().state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
assertEquals(listOf(a.info.legalIdentity.owningKey), (state.first().state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
// Get contract state from the vault.
val firstState = databaseTransaction(a.database) { a.vault.unconsumedStates<ContractState>().single() }
assertTrue(firstState.state.data is CashV2.State, "Contract state is upgraded to the new version.")
assertEquals(Amount(1000000, USD).`issued by`(a.info.legalIdentity.ref(1)), (firstState.state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
assertEquals(listOf(a.info.legalIdentity.owningKey), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
}
class CashV2 : UpgradedContract<Cash.State, CashV2.State> {

View File

@ -1,5 +1,6 @@
package net.corda.core.node
import com.esotericsoftware.kryo.Kryo
import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
@ -11,7 +12,9 @@ import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.MEGA_CORP
import net.corda.testing.node.MockAttachmentStorage
import org.apache.commons.io.IOUtils
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@ -75,6 +78,16 @@ class AttachmentClassLoaderTests {
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
lateinit var kryo: Kryo
lateinit var kryo2: Kryo
@Before
fun setup() {
// Do not release these back to the pool, since we do some unorthodox modifications to them below.
kryo = p2PKryo().borrow()
kryo2 = p2PKryo().borrow()
}
@Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
val child = ClassLoaderForTests()
@ -205,7 +218,6 @@ class AttachmentClassLoaderTests {
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val kryo = createKryo()
kryo.classLoader = cl
kryo.addToWhitelist(contract.javaClass)
@ -224,7 +236,6 @@ class AttachmentClassLoaderTests {
assertNotNull(data.contract)
val kryo2 = createKryo()
kryo2.addToWhitelist(data.contract.javaClass)
val bytes = data.serialize(kryo2)
@ -236,15 +247,25 @@ class AttachmentClassLoaderTests {
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val kryo = createKryo()
kryo.classLoader = cl
kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl))
val state2 = bytes.deserialize(kryo)
assertEquals(cl, state2.contract.javaClass.classLoader)
assertNotNull(state2)
// We should be able to load same class from a different class loader and have them be distinct.
val cl2 = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
kryo.classLoader = cl2
kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl2))
val state3 = bytes.deserialize(kryo)
assertEquals(cl2, state3.contract.javaClass.classLoader)
assertNotNull(state3)
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = ATTACHMENT_TEST_PROGRAM_ID.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
@ -263,31 +284,30 @@ class AttachmentClassLoaderTests {
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val kryo = createKryo()
kryo.addToWhitelist(contract.javaClass)
kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$State", true, child))
kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$Commands\$Create", true, child))
// todo - think about better way to push attachmentStorage down to serializer
kryo.attachmentStorage = storage
val bytes = kryo.withAttachmentStorage(storage) {
val attachmentRef = importJar(storage)
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize(kryo)
val kryo2 = createKryo()
wireTransaction.serialize(kryo)
}
// use empty attachmentStorage
kryo2.attachmentStorage = storage
kryo2.withAttachmentStorage(storage) {
val copiedWireTransaction = bytes.deserialize(kryo2)
val copiedWireTransaction = bytes.deserialize(kryo2)
assertEquals(1, copiedWireTransaction.outputs.size)
val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
assertEquals(1, copiedWireTransaction.outputs.size)
val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
}
}
@Test
@ -297,26 +317,24 @@ class AttachmentClassLoaderTests {
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val kryo = createKryo()
// todo - think about better way to push attachmentStorage down to serializer
kryo.attachmentStorage = storage
val attachmentRef = importJar(storage)
val bytes = kryo.withAttachmentStorage(storage) {
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize(kryo)
val kryo2 = createKryo()
// use empty attachmentStorage
kryo2.attachmentStorage = MockAttachmentStorage()
val e = assertFailsWith(MissingAttachmentsException::class) {
bytes.deserialize(kryo2)
wireTransaction.serialize(kryo)
}
// use empty attachmentStorage
kryo2.withAttachmentStorage(MockAttachmentStorage()) {
val e = assertFailsWith(MissingAttachmentsException::class) {
bytes.deserialize(kryo2)
}
assertEquals(attachmentRef, e.ids.single())
}
assertEquals(attachmentRef, e.ids.single())
}
}

View File

@ -1,5 +1,6 @@
package net.corda.core.serialization
import com.esotericsoftware.kryo.Kryo
import com.google.common.primitives.Ints
import net.corda.core.crypto.*
import net.corda.core.messaging.Ack
@ -7,16 +8,27 @@ import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.slf4j.LoggerFactory
import java.io.InputStream
import java.security.Security
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class KryoTests {
private val kryo = createKryo()
private lateinit var kryo: Kryo
@Before
fun setup() {
// We deliberately do not return this, since we do some unorthodox registering below and do not want to pollute the pool.
kryo = p2PKryo().borrow()
}
@Test
fun ok() {
@ -112,6 +124,14 @@ class KryoTests {
assertEquals(meta2, meta)
}
@Test
fun `serialize - deserialize Logger`() {
val logger = LoggerFactory.getLogger("aName")
val logger2 = logger.serialize(storageKryo()).deserialize(storageKryo())
assertEquals(logger.name, logger2.name)
assertTrue(logger === logger2)
}
@CordaSerializable
private data class Person(val name: String, val birthday: Instant?)

View File

@ -15,12 +15,13 @@ class SerializationTokenTest {
@Before
fun setup() {
kryo = threadLocalStorageKryo()
kryo = storageKryo().borrow()
}
@After
fun cleanup() {
SerializeAsTokenSerializer.clearContext(kryo)
storageKryo().release(kryo)
}
// Large tokenizable object so we can tell from the smaller number of serialized bytes it was actually tokenized
@ -38,7 +39,7 @@ class SerializationTokenTest {
@Test
fun `write token and read tokenizable`() {
val tokenizableBefore = LargeTokenizable()
val context = SerializeAsTokenContext(tokenizableBefore, kryo)
val context = SerializeAsTokenContext(tokenizableBefore, storageKryo())
SerializeAsTokenSerializer.setContext(kryo, context)
val serializedBytes = tokenizableBefore.serialize(kryo)
assertThat(serializedBytes.size).isLessThan(tokenizableBefore.numBytes)
@ -51,7 +52,7 @@ class SerializationTokenTest {
@Test
fun `write and read singleton`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = SerializeAsTokenContext(tokenizableBefore, kryo)
val context = SerializeAsTokenContext(tokenizableBefore, storageKryo())
SerializeAsTokenSerializer.setContext(kryo, context)
val serializedBytes = tokenizableBefore.serialize(kryo)
val tokenizableAfter = serializedBytes.deserialize(kryo)
@ -61,7 +62,7 @@ class SerializationTokenTest {
@Test(expected = UnsupportedOperationException::class)
fun `new token encountered after context init`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = SerializeAsTokenContext(emptyList<Any>(), kryo)
val context = SerializeAsTokenContext(emptyList<Any>(), storageKryo())
SerializeAsTokenSerializer.setContext(kryo, context)
tokenizableBefore.serialize(kryo)
}
@ -69,9 +70,9 @@ class SerializationTokenTest {
@Test(expected = UnsupportedOperationException::class)
fun `deserialize unregistered token`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = SerializeAsTokenContext(emptyList<Any>(), kryo)
val context = SerializeAsTokenContext(emptyList<Any>(), storageKryo())
SerializeAsTokenSerializer.setContext(kryo, context)
val serializedBytes = tokenizableBefore.toToken(SerializeAsTokenContext(emptyList<Any>(), kryo)).serialize(kryo)
val serializedBytes = tokenizableBefore.toToken(SerializeAsTokenContext(emptyList<Any>(), storageKryo())).serialize(kryo)
serializedBytes.deserialize(kryo)
}
@ -84,7 +85,7 @@ class SerializationTokenTest {
@Test(expected = KryoException::class)
fun `deserialize non-token`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = SerializeAsTokenContext(tokenizableBefore, kryo)
val context = SerializeAsTokenContext(tokenizableBefore, storageKryo())
SerializeAsTokenSerializer.setContext(kryo, context)
val stream = ByteArrayOutputStream()
Output(stream).use {
@ -106,7 +107,7 @@ class SerializationTokenTest {
@Test(expected = KryoException::class)
fun `token returns unexpected type`() {
val tokenizableBefore = WrongTypeSerializeAsToken()
val context = SerializeAsTokenContext(tokenizableBefore, kryo)
val context = SerializeAsTokenContext(tokenizableBefore, storageKryo())
SerializeAsTokenSerializer.setContext(kryo, context)
val serializedBytes = tokenizableBefore.serialize(kryo)
serializedBytes.deserialize(kryo)

View File

@ -4,7 +4,7 @@ import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoSerializable
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import net.corda.core.serialization.createInternalKryo
import net.corda.core.serialization.createTestKryo
import net.corda.core.serialization.serialize
import org.junit.Before
import org.junit.Test
@ -106,7 +106,7 @@ class ProgressTrackerTest {
}
}
val kryo = createInternalKryo().apply {
val kryo = createTestKryo().apply {
// This is required to make sure Kryo walks through the auto-generated members for the lambda below.
fieldSerializerConfig.isIgnoreSyntheticFields = false
}

Binary file not shown.

View File

@ -3,6 +3,26 @@ Changelog
Here are brief summaries of what's changed between each snapshot release.
UNRELEASED
----------
API changes:
* The new Jackson module provides JSON/YAML serialisers for common Corda datatypes. If you have previously been
using the JSON support in the standalone web server, please be aware that amounts are now serialised as strings
instead of { quantity, token } pairs as before. The old format is still accepted, but new JSON will be produced
using strings like "1000.00 USD" when writing. You can use any format supported by ``Amount.parseCurrency``
as input.
Milestone 10
------------
* Configuration:
* Replace ``artemisPort`` with ``p2pPort`` in Gradle configuration
* Replace ``artemisAddress`` with ``p2pAddress`` in node configuration
* Added ``rpcAddress`` in node configuration
Milestone 9.1
-------------

View File

@ -38,8 +38,9 @@ NetworkMapService plus Simple Notary configuration file.
nearestCity : "London"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "localhost:12345"
webAddress : "localhost:12346"
p2pAddress : "localhost:12345"
rpcAddress : "localhost:12346"
webAddress : "localhost:12347"
extraAdvertisedServiceIds : []
useHTTPS : false
devMode : true
@ -74,13 +75,15 @@ path to the node's base directory.
Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently
the only configuration that has been tested, although in the future full support for other storage layers will be validated.
:artemisAddress: The host and port on which the node is available for protocol operations over ArtemisMQ.
:messagingServerAddress: The address of the ArtemisMQ broker instance. If not provided the node will run one locally.
:p2pAddress: The host and port on which the node is available for protocol operations over ArtemisMQ.
.. note:: In practice the ArtemisMQ messaging services bind to all local addresses on the specified port. However,
note that the host is the included as the advertised entry in the NetworkMapService. As a result the value listed
here must be externally accessible when running nodes across a cluster of machines.
:messagingServerAddress: The address of the ArtemisMQ broker instance. If not provided the node will run one locally.
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
:webAddress: The host and port on which the bundled webserver will listen if it is started.

View File

@ -57,9 +57,12 @@ case the ``node_dir`` is the location where your node server's JAR and configura
Starting your node
------------------
Now you have a node server with your app installed, you can run it by navigating to ``<node_dir>`` and running
Now you have a node server with your app installed, you can run it by navigating to ``<node_dir>`` and running:
java -jar corda.jar
.. code-block:: shell
Windows: java -jar corda.jar
UNIX: ./corda.jar
The plugin should automatically be registered and the configuration file used.
@ -67,7 +70,7 @@ The plugin should automatically be registered and the configuration file used.
The configuration file and workspace paths can be overidden on the command line e.g.
``java -jar corda.jar --config-file=test.conf --base-directory=/opt/r3corda/nodes/test``.
``./corda.jar --config-file=test.conf --base-directory=/opt/r3corda/nodes/test``.
Otherwise the workspace folder for the node is the current working path.
@ -99,35 +102,22 @@ at the present time, and should certainly be treated as read-only.
Building against Corda
----------------------
.. warning:: This feature is subject to rapid change
Corda now supports publishing to Maven local to build against it. To publish to Maven local run the following in the
root directory of Corda
To publish to your local Maven repository (in ``~/.m2`` on Unix and ``%HOMEPATH%\.m2`` on Windows) run the following
in the root directory of the Corda code:
.. code-block:: shell
./gradlew install
This will publish corda-$version.jar, finance-$version.jar, core-$version.jar and node-$version.jar to the
group net.corda. You can now depend on these as you normally would a Maven dependency.
Gradle plugins for CorDapps
---------------------------
group net.corda. You can now depend on these as you normally would a Maven dependency, using the group id
``net.corda``.
There are several Gradle plugins that reduce your build.gradle boilerplate and make development of CorDapps easier.
The available plugins are in the gradle-plugins directory of the Corda repository.
Building Gradle plugins
~~~~~~~~~~~~~~~~~~~~~~~
To install to your local Maven repository the plugins that CorDapp gradle files require, run the following from the
root of the Corda project:
.. code-block:: text
./gradlew install
The plugins will now be installed to your local Maven repository in ~/.m2 on Unix and %HOMEPATH%\.m2 on Windows.
To install to your local Maven repository the plugins that CorDapp gradle files require, enter the ``gradle-plugins``
directory and then run ``../gradle install``. The plugins will now be installed to your local Maven repository.
Using Gradle plugins
~~~~~~~~~~~~~~~~~~~~
@ -135,11 +125,6 @@ Using Gradle plugins
To use the plugins, if you are not already using the CorDapp template project, you must modify your build.gradle. Add
the following segments to the relevant part of your build.gradle.
Template build.gradle
~~~~~~~~~~~~~~~~~~~~~
To build against Corda and the plugins that CorDapps use, update your build.gradle to contain the following:
.. code-block:: groovy
buildscript {
@ -214,24 +199,27 @@ is a three node example;
name "Controller"
nearestCity "London"
advertisedServices = [ "corda.notary.validating" ]
artemisPort 12345
webPort 12346
p2pPort 10002
rpcPort 10003
webPort 10004
cordapps []
}
node {
name "NodeA"
nearestCity "London"
advertisedServices = []
artemisPort 31337
webPort 31339
p2pPort 10005
rpcPort 10006
webPort 10007
cordapps []
}
node {
name "NodeB"
nearestCity "New York"
advertisedServices = []
artemisPort 31338
webPort 31340
p2pPort 10008
rpcPort 10009
webPort 10010
cordapps []
}
}
@ -239,10 +227,9 @@ is a three node example;
You can create more configurations with new tasks that extend Cordform.
New nodes can be added by simply adding another node block and giving it a different name, directory and ports. When you
run this task it will install the nodes to the directory specified and a script will be generated (for UNIX users only
at present) to run the nodes with one command (``runnodes``). On MacOS X this script will run each node in a new
terminal tab, and on Linux it will open up a new XTerm for each node. On Windows the (``runnodes.bat``) script will run
one node per window.
run this task it will install the nodes to the directory specified and a script will be generated to run the nodes with
one command (``runnodes``). On MacOS X this script will run each node in a new terminal tab, and on Linux it will open
up a new XTerm for each node. On Windows the (``runnodes.bat``) script will run one node per window.
Other CorDapps can also be specified if they are already specified as classpath or compile dependencies in your
``build.gradle``.

View File

@ -40,7 +40,7 @@ compileTestJava.dependsOn tasks.getByPath(':node:capsule:buildCordaJAR')
dependencies {
compile project(':core')
compile project(':client')
compile project(':client:jfx')
testCompile project(':test-utils')
compile "org.graphstream:gs-core:1.3"
@ -49,7 +49,7 @@ dependencies {
}
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
runtime project(path: ":node:webserver:webcapsule", configuration: 'runtimeArtifacts')
}
mainClassName = "net.corda.docs.ClientRpcTutorialKt"
@ -80,16 +80,18 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
name "Notary"
nearestCity "London"
advertisedServices = ["corda.notary.validating"]
artemisPort 10002
webPort 10003
p2pPort 10002
rpcPort 10003
webPort 10004
cordapps = []
}
node {
name "Alice"
nearestCity "London"
advertisedServices = []
artemisPort 10004
webPort 10005
p2pPort 10005
rpcPort 10006
webPort 10007
cordapps = []
rpcUsers = [
['user' : "user",

View File

@ -4,13 +4,11 @@ import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.issuedBy
import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.toFuture
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.node.driver.driver
@ -92,10 +90,7 @@ class IntegrationTestingTutorial {
// START 5
for (i in 1 .. 10) {
bobProxy.startFlow(::CashPaymentFlow,
i.DOLLARS.issuedBy(alice.nodeInfo.legalIdentity.ref(issueRef)),
alice.nodeInfo.legalIdentity
).returnValue.getOrThrow()
bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.legalIdentity).returnValue.getOrThrow()
}
aliceVaultUpdates.expectEvents {

View File

@ -122,7 +122,7 @@ fun generateTransactions(proxy: CordaRPCOps) {
ownedQuantity -= quantity
} else if (ownedQuantity > 1000 && n < 0.7) {
val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000))
proxy.startFlow(::CashPaymentFlow, Amount(quantity, Issued(meAndRef, USD)), me)
proxy.startFlow(::CashPaymentFlow, Amount(quantity, USD), me)
} else {
val quantity = Math.abs(random.nextLong() % 1000)
proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, me, notary)

View File

@ -24,7 +24,7 @@ import java.util.*
object FxTransactionDemoTutorial {
// Would normally be called by custom service init in a CorDapp
fun registerFxProtocols(pluginHub: PluginServiceHub) {
pluginHub.registerFlowInitiator(ForeignExchangeFlow::class, ::ForeignExchangeRemoteFlow)
pluginHub.registerFlowInitiator(ForeignExchangeFlow::class.java, ::ForeignExchangeRemoteFlow)
}
}

View File

@ -17,7 +17,7 @@ import java.time.Duration
object WorkflowTransactionBuildTutorial {
// Would normally be called by custom service init in a CorDapp
fun registerWorkflowProtocols(pluginHub: PluginServiceHub) {
pluginHub.registerFlowInitiator(SubmitCompletionFlow::class, ::RecordCompletionFlow)
pluginHub.registerFlowInitiator(SubmitCompletionFlow::class.java, ::RecordCompletionFlow)
}
}

View File

@ -2,7 +2,7 @@ myLegalName : "Notary Service"
nearestCity : "London"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "my-network-map:10000"
p2pAddress : "my-network-map:10000"
webAddress : "localhost:10001"
extraAdvertisedServiceIds : []
useHTTPS : false

View File

@ -8,8 +8,9 @@ dataSourceProperties : {
"dataSource.user" : sa
"dataSource.password" : ""
}
artemisAddress : "my-corda-node:10002"
webAddress : "localhost:10003"
p2pAddress : "my-corda-node:10002"
rpcAddress : "my-corda-node:10003"
webAddress : "localhost:10004"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "my-network-map:10000"
@ -21,4 +22,4 @@ rpcUsers : [
]
devMode : true
// Certificate signing service will be hosted by R3 in the near future.
//certificateSigningService : "https://testnet.certificate.corda.net"
//certificateSigningService : "https://testnet.certificate.corda.net"

View File

@ -413,9 +413,6 @@ This code is longer but no more complicated. Here are some things to pay attenti
As you can see, the flow logic is straightforward and does not contain any callbacks or network glue code, despite
the fact that it takes minimal resources and can survive node restarts.
.. warning:: In the current version of the platform, exceptions thrown during flow execution are not propagated
back to the sender. A thorough error handling and exceptions framework will be in a future version of the platform.
Progress tracking
-----------------
@ -530,10 +527,8 @@ Future features
The flow framework is a key part of the platform and will be extended in major ways in future. Here are some of
the features we have planned:
* Identity based addressing
* Exception management, with a "flow hospital" tool to manually provide solutions to unavoidable
problems (e.g. the other side doesn't know the trade)
* Being able to interact with internal apps and tools via RPC
* Being able to interact with people, either via some sort of external ticketing system, or email, or a custom UI.
For example to implement human transaction authorisations.
* A standard library of flows that can be easily sub-classed by local developers in order to integrate internal

View File

@ -5,12 +5,12 @@ Welcome to the Corda documentation!
current state of the code. `Read the docs for milestone release M9.0 <https://docs.corda.net/releases/release-M9.0/>`_.
`Corda <https://www.corda.net/>`_ is an open-source distributed ledger platform. The latest *milestone* (i.e. stable)
release is M9.0. The codebase is on `GitHub <https://github.com/corda>`_, and our community can be found on
release is M9.2. The codebase is on `GitHub <https://github.com/corda>`_, and our community can be found on
`Slack <https://slack.corda.net/>`_ and in our `forum <https://discourse.corda.net/>`_.
If you're new to Corda, you should start by learning about its motivating vision and architecture. A good introduction
is the `Introduction to Corda webinar <https://vimeo.com/192757743/c2ec39c1e1>`_ and the `Introductory white paper`_. As
they become more familiar with Corda, readers with a technical background will also want to dive into the `Technical white paper`_,
you become more familiar with Corda, readers with a technical background will also want to dive into the `Technical white paper`_,
which describes the platform's envisioned end-state.
.. note:: Corda training is now available in London, New York and Singapore! `Learn more. <https://www.corda.net/corda-training/>`_

View File

@ -1,27 +1,51 @@
What's included?
================
The Corda prototype currently includes:
This Corda early access preview includes:
* A collection of samples, for instance a web app demo that uses it to implement IRS trading.
* A template app you can use to get started, and tutorial app that teaches you the basics.
* A peer to peer network with message persistence and delivery retries.
* Key data structures for defining contracts and states.
* Smart contracts, which you can find in the :doc:`contract-catalogue`.
* Algorithms that work with them, such as serialising, hashing, signing, and verification of the signatures.
* API documentation and tutorials (what you're reading).
* A business process orchestration framework.
* A business process workflow framework.
* Notary infrastructure for precise timestamping, and elimination of double spending without a blockchain.
* A simple REST API, and a web app demo that uses it to present a frontend for IRS trading.
* A simple RPC API.
* A user interface for administration.
Some things it does not currently include but should gain later are:
* Sandboxing, distribution or publication of smart contract code
* A user interface for administration
* Sandboxing, distribution and publication of smart contract code.
* A well specified wire protocol.
* An identity framework.
The prototype's goal is rapid exploration of ideas. Therefore in places it takes shortcuts that a production system
would not in order to boost productivity:
The open source version of Corda is designed for developers exploring how to write apps. It is not intended to
be production grade software. For example it uses an embedded SQL database and doesn't yet have connectivity
support for mainstream SQL vendors (Oracle, Postgres, MySQL, SQL Server etc). It hasn't been security audited
and the APIs change in every release.
* It uses an object graph serialization framework instead of a well specified, vendor neutral protocol.
* There's currently no permissioning framework.
* Some privacy techniques aren't implemented yet.
* It uses an embedded SQL database and doesn't yet have connectivity support for mainstream SQL vendors (Oracle,
Postgres, MySQL, SQL Server etc).
Source tree layout
------------------
The Corda repository comprises the following folders:
* **buildSrc** contains necessary gradle plugins to build Corda.
* **client** contains libraries for connecting to a node, working with it remotely and binding server-side data to JavaFX UI.
* **config** contains logging configurations and the default node configuration file.
* **core** containing the core Corda libraries such as crypto functions, types for Corda's building blocks: states,
contracts, transactions, attachments, etc. and some interfaces for nodes and protocols.
* **docs** contains the Corda docsite in restructured text format as well as the built docs in html. The docs can be
accessed via ``/docs/index.html`` from the root of the repo.
* **finance** defines a range of elementary contracts (and associated schemas) and protocols, such as abstract fungible
assets, cash, obligation and commercial paper.
* **gradle** contains the gradle wrapper which you'll use to execute gradle commands.
* **gradle-plugins** contains some additional plugins which we use to deploy Corda nodes.
* **lib** contains some dependencies.
* **node** contains the core code of the Corda node (eg: node driver, servlets, node services, messaging, persistence).
* **node-api** contains data structures shared between the node and the client module, e.g. types sent via RPC.
* **node-schemas** contains entity classes used to represent relational database tables.
* **samples** contains all our Corda demos and code samples.
* **test-utils** contains some utilities for unit testing contracts ( the contracts testing DSL) and protocols (the
mock network) implementation.
* **tools** contains the explorer which is a GUI front-end for Corda.

View File

@ -105,11 +105,13 @@ Validation
One of the design decisions for a notary is whether or not to **validate** a transaction before accepting it.
If a transaction is not checked for validity, it opens the platform to "denial of state" attacks, where anyone can build an invalid transaction consuming someone else's states and submit it to the notary to get the states "blocked".
However, if the transaction is validated, this requires the notary to be able to see the full contents of the transaction in question and its dependencies.
This is an obvious privacy leak.
If a transaction is not checked for validity, it opens the platform to "denial of state" attacks, where anyone can build
an invalid transaction consuming someone else's states and submit it to the notary to get the states blocked. However,
if the transaction is validated, this requires the notary to be able to see the full contents of the transaction in
question and its dependencies. This is an obvious privacy leak.
The platform is flexible and currently supports both validating and non-validating notary implementations -- a party can select which one to use based on its own privacy requirements.
The platform is flexible and currently supports both validating and non-validating notary implementations -- a
party can select which one to use based on its own privacy requirements.
.. note:: In the non-validating model, the "denial of state" attack is partially alleviated by requiring the calling
party to authenticate and storing its identity for the request. The conflict information returned by the notary
@ -117,9 +119,6 @@ The platform is flexible and currently supports both validating and non-validati
conflicting transaction is valid, the current one is aborted; if not, a dispute can be raised and the input states
of the conflicting invalid transaction are "un-committed" (via a legal process).
.. note:: At present, all notaries can see the entire contents of a submitted transaction. A future piece of work
will enable the processing of :doc:`merkle-trees`, thus providing data hiding of sensitive information.
Timestamping
------------

View File

@ -132,7 +132,7 @@ downloading any missing transactions into local storage and validating them. The
.. note:: Non-validating notaries assume transaction validity and do not request transaction data or their dependencies
beyond the list of states consumed.
The tutorial " :doc:`tutorial-contract` "provides a hand-ons walk-through using these concepts.
The tutorial ":doc:`tutorial-contract`" provides a hand-ons walk-through using these concepts.
Transaction Representation
^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -19,8 +19,8 @@ The security model plays a role in the following areas:
beyond the list of states consumed (and thus, their level of trust is much lower and exposed to malicious use of transaction inputs).
From an algorithm perspective, Corda currently provides a distributed notary implementation that uses Raft.
.. note:: Future notary algorithms may include BFT and hardware assisted non-BFT algorithms (where non-BFT algorithms
are converted into a more trusted form using remote attestation and hardware protection).
.. note:: A byzantine fault tolerant notary based on the BFT-SMaRT algorithm is included in the code, but is
still incubating and is not yet ready for use.
* Authentication, authorisation and entitlements:
Network permissioning, including node to node authentication, is performed using TLS and certificates.
@ -28,7 +28,6 @@ The security model plays a role in the following areas:
.. warning:: API level authentication (RPC, Web) is currently simple username/password for demonstration purposes and will be revised.
Similarly, authorisation is currently based on permission groups applied to flow execution.
This is subject to design review with views to selecting a proven, mature entitlements solution.
Privacy techniques

View File

@ -104,8 +104,8 @@ validated user is the X.500 subject DN of the client TLS certificate and we assu
the peer. This allows the flow framework to authentically determine the ``Party`` initiating a new flow. For RPC clients
the validated user is the username itself and the RPC framework uses this to determine what permissions the user has.
.. note:: ``Party`` lookup is currently done by the legal name which isn't guaranteed to be unique. A future version will
use the full X.500 name as it can provide additional structures for uniqueness.
.. note:: ``Party`` lookup is currently done by the legal name. A future version will use the full X.500 name as
it can provide additional structures for uniqueness.
The broker also does host verification when connecting to another peer. It checks that the TLS certificate common name
matches with the advertised legal name from the network map service.

View File

@ -63,15 +63,15 @@ The Demo Nodes can be started in one of two modes:
.. note:: 5 Corda nodes will be created on the following port on localhost by default.
* Notary -> 20001
* Alice -> 20003
* Bob -> 20005
* UK Bank Plc -> 20008 (*Issuer node*)
* USA Bank Corp -> 20009 (*Issuer node*)
* Notary -> 20003 (Does not accept logins)
* Alice -> 20006
* Bob -> 20009
* UK Bank Plc -> 20012 (*Issuer node*)
* USA Bank Corp -> 20015 (*Issuer node*)
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``.
Please note you are not allowed to connect to the notary.
Please note you are not allowed to login to the notary.
.. note:: Alternatively, you may start the demo nodes from within IntelliJ using either of the run configurations
``Explorer - demo nodes`` or ``Explorer - demo nodes (simulation)``

View File

@ -346,11 +346,13 @@ external legacy systems by insertion of unpacked data into existing
tables. To enable these features the contract state must implement the
``QueryableState`` interface to define the mappings.
Node Web Server
---------------
Corda Web Server
----------------
A web server comes bundled with the node by default, but is not started
automatically. This web server exposes both RPC backed API calls and
static content serving. The web server is not automatically started,
you must explicitly start it in the node driver or define a web port
in your `Cordformation`_ configuration.
A simple web server is provided that embeds the Jetty servlet container.
The Corda web server is not meant to be used for real, production-quality
web apps. Instead it shows one example way of using Corda RPC in web apps
to provide a REST API on top of the Corda native RPC mechanism.
.. note:: The Corda web server may be removed in future and replaced with
sample specific webapps using a standard framework like Spring Boot.

View File

@ -2,16 +2,13 @@ Network permissioning
=====================
The keystore located in ``<workspace>/certificates/sslkeystore.jks`` is required to connect to the Corda network securely.
In development mode (when ``devMode = true``, see :doc:`corda-configuration-file` for more information) a pre-configured
In development mode (when ``devMode = true``, see ":doc:`corda-configuration-file`" for more information) a pre-configured
keystore will be used if the keystore does not exist. This is to ensure developers can get the nodes working as quickly
as possible.
However this is not secure for the real network. This documentation will explain the procedure of obtaining a signed
certificate for TestNet.
.. warning:: The TestNet has not been setup yet as of Milestone 8 release. You will not be able to connect to the
certificate signing server.
Initial Registration
--------------------
@ -55,4 +52,3 @@ You can also specify the location of ``node.conf`` with ``--config-file`` flag i
A ``certificates`` folder containing the keystore and trust store will be created in the base directory when the process is completed.
.. warning:: The keystore is protected by the keystore password from the node configuration file. The password should kept safe to protect the private key and certificate.
.. note:: Password encryption in node configuration will be supported in subsequent release.

View File

@ -4,6 +4,15 @@ Release notes
Here are release notes for each snapshot release from M9 onwards. This includes guidance on how to upgrade code from
the previous milestone release.
Milestone 10
------------
Important: There are configuration changes in M10 due to the split of the Artemis port into separate P2P and RPC
ports. To upgrade, you *must*:
1. In Gradle build configurations replace any references to ``artemisPort`` with ``p2pPort``.
2. In node configurations replace ``artemisAddress`` with ``p2pAddress``.
Milestone 9
-----------
@ -46,4 +55,4 @@ clients.
There have also been dozens of bug fixes, performance improvements and usability tweaks. Upgrading is definitely
worthwhile and will only take a few minutes for most apps.
For a full list of changes please see :doc:`change-log`.
For a full list of changes please see :doc:`change-log`.

View File

@ -17,9 +17,9 @@ The demos can be run either from the command line, or from inside IntelliJ. Runn
recommended if you just want to see the demos run, whereas using IntelliJ can be helpful if you want to debug or
extend the demos. For more details about running via the command line or from within IntelliJ, see :doc:`CLI-vs-IDE`.
.. note:: If you are running the demos from the command line in Unix, you may have to install xterm.
If any of the demos don't work, please raise an issue on GitHub.
.. note:: If any of the demos don't work, please raise an issue on GitHub.
.. note:: If you are running the demos from the command line in Linux (but not macOS), you may have to install xterm.
.. _trader-demo:
@ -235,7 +235,7 @@ To run from IntelliJ:
4. Run ``Bank Of Corda Demo: Run Web Cash Issue`` to request issuance of some cash on behalf of Big Corporation via HTTP
.. note:: To verify that the Bank of Corda node is alive and running, navigate to the following URL:
http://localhost:10005/api/bank/date
http://localhost:10007/api/bank/date
.. note:: The Bank of Corda node explicitly advertises with a node service type as follows:
``advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer"))))``
@ -259,8 +259,8 @@ Launch the Explorer application to visualize the issuance and transfer of cash f
Using the following login details:
- For the Bank of Corda node: localhost / port 10004 / username bankUser / password test
- For the Big Corporation node: localhost / port 10006 / username bigCorpUser / password test
- For the Bank of Corda node: localhost / port 10006 / username bankUser / password test
- For the Big Corporation node: localhost / port 10009 / username bigCorpUser / password test
See https://docs.corda.net/node-explorer.html for further details on usage.

Some files were not shown because too many files have changed in this diff Show More