mirror of
https://github.com/corda/corda.git
synced 2025-01-14 16:59:52 +00:00
Merge remote-tracking branch 'open/master'
This commit is contained in:
commit
8665e92960
5
.gitignore
vendored
5
.gitignore
vendored
@ -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
|
@ -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="build/attachment-demo-nodes/Bank B/certificates"" />
|
||||
<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" />
|
||||
|
@ -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="build/attachment-demo-nodes/Bank A/certificates"" />
|
||||
<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" />
|
||||
|
@ -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="build/trader-demo-nodes/Bank A/certificates"" />
|
||||
<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" />
|
||||
|
@ -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="build/trader-demo-nodes/Bank B/certificates"" />
|
||||
<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" />
|
||||
|
47
build.gradle
47
build.gradle
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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"))))
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
@ -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()
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ListChangeListener
|
@ -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.*
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.beans.Observable
|
||||
import javafx.beans.value.ObservableValue
|
@ -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
|
@ -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
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.MapChangeListener
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.property.SimpleObjectProperty
|
@ -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))
|
||||
}
|
||||
}
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.collections.FXCollections
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
@ -1,4 +1,4 @@
|
||||
package net.corda.client.fxutils
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
@ -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
|
@ -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
44
client/mock/build.gradle
Normal 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')
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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. */
|
||||
|
@ -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 {
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 } }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
74
core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java
Normal file
74
core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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> {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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?)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
BIN
docs/build/html/_static/corda-introductory-whitepaper-jp.pdf
vendored
Normal file
BIN
docs/build/html/_static/corda-introductory-whitepaper-jp.pdf
vendored
Normal file
Binary file not shown.
BIN
docs/source/_static/corda-introductory-whitepaper-jp.pdf
Normal file
BIN
docs/source/_static/corda-introductory-whitepaper-jp.pdf
Normal file
Binary file not shown.
@ -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
|
||||
-------------
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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``.
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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/>`_
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
------------
|
||||
|
||||
|
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)``
|
||||
|
@ -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.
|
@ -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.
|
||||
|
@ -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`.
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user