mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
Merge pull request #89 from corda/christians_os_merge_20171031
OpenSource -> Enterprise
This commit is contained in:
commit
7800b6cb4a
@ -2436,6 +2436,20 @@ public interface net.corda.core.serialization.ClassWhitelist
|
||||
##
|
||||
public @interface net.corda.core.serialization.CordaSerializable
|
||||
##
|
||||
public @interface net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
public abstract String new()
|
||||
public abstract String old()
|
||||
##
|
||||
public @interface net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
public abstract net.corda.core.serialization.CordaSerializationTransformEnumDefault[] value()
|
||||
##
|
||||
public @interface net.corda.core.serialization.CordaSerializationTransformRename
|
||||
public abstract String from()
|
||||
public abstract String to()
|
||||
##
|
||||
public @interface net.corda.core.serialization.CordaSerializationTransformRenames
|
||||
public abstract net.corda.core.serialization.CordaSerializationTransformRename[] value()
|
||||
##
|
||||
public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
public abstract int version()
|
||||
##
|
||||
@ -2476,19 +2490,19 @@ public static final class net.corda.core.serialization.SerializationContext$UseC
|
||||
public static net.corda.core.serialization.SerializationContext$UseCase valueOf(String)
|
||||
public static net.corda.core.serialization.SerializationContext$UseCase[] values()
|
||||
##
|
||||
public final class net.corda.core.serialization.SerializationDefaults extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getCHECKPOINT_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getP2P_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getRPC_CLIENT_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getRPC_SERVER_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationFactory getSERIALIZATION_FACTORY()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getSTORAGE_CONTEXT()
|
||||
public final void setCHECKPOINT_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public final void setP2P_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public final void setRPC_CLIENT_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public final void setRPC_SERVER_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public final void setSERIALIZATION_FACTORY(net.corda.core.serialization.SerializationFactory)
|
||||
public final void setSTORAGE_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public final class net.corda.core.serialization.SerializationDefaults extends java.lang.Object implements net.corda.core.serialization.internal.SerializationEnvironment
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getCHECKPOINT_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getP2P_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRPC_CLIENT_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRPC_SERVER_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationFactory getSERIALIZATION_FACTORY()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getSTORAGE_CONTEXT()
|
||||
public void setCHECKPOINT_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public void setP2P_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public void setRPC_CLIENT_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public void setRPC_SERVER_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public void setSERIALIZATION_FACTORY(net.corda.core.serialization.SerializationFactory)
|
||||
public void setSTORAGE_CONTEXT(net.corda.core.serialization.SerializationContext)
|
||||
public static final net.corda.core.serialization.SerializationDefaults INSTANCE
|
||||
##
|
||||
public abstract class net.corda.core.serialization.SerializationFactory extends java.lang.Object
|
||||
|
@ -10,5 +10,22 @@
|
||||
<cpe>cpe:/a:apache:struts:2.0.0</cpe>
|
||||
</suppress>
|
||||
-->
|
||||
|
||||
<suppress>
|
||||
<!-- Vulnerability when using SSLv2 Hello messages. Corda uses TLS1.2-->
|
||||
<notes><![CDATA[file name: catalyst-netty-1.1.2.jar]]></notes>
|
||||
<gav regex="true">^io\.atomix\.catalyst:catalyst-netty:.*$</gav>
|
||||
<cve>CVE-2014-3488</cve>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<!-- Vulnerability to LDAP poisoning attacks. Corda doesn't use LDAP-->
|
||||
<notes><![CDATA[file name: groovy-all-1.8.9.jar]]></notes>
|
||||
<gav regex="true">^commons-cli:commons-cli:.*$</gav>
|
||||
<cve>CVE-2016-6497</cve>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<!-- Java objects serialization disabled in Corda -->
|
||||
<notes><![CDATA[file name: groovy-all-1.8.9.jar]]></notes>
|
||||
<gav regex="true">^commons-cli:commons-cli:.*$</gav>
|
||||
<cve>CVE-2015-3253</cve>
|
||||
</suppress>
|
||||
</suppressions>
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -11,6 +11,7 @@ tags
|
||||
|
||||
.gradle
|
||||
local.properties
|
||||
.gradletasknamecache
|
||||
|
||||
# General build files
|
||||
**/build/*
|
||||
@ -35,6 +36,7 @@ lib/quasar.jar
|
||||
.idea/dataSources
|
||||
.idea/markdown-navigator
|
||||
.idea/runConfigurations
|
||||
.idea/dictionaries
|
||||
/gradle-plugins/.idea/
|
||||
|
||||
# Include the -parameters compiler option by default in IntelliJ required for serialization.
|
||||
|
46
.idea/compiler.xml
generated
46
.idea/compiler.xml
generated
@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<module name="api-scanner_main" target="1.8" />
|
||||
<module name="api-scanner_test" target="1.8" />
|
||||
<bytecodeTargetLevel>
|
||||
<module name="attachment-demo_integrationTest" target="1.8" />
|
||||
<module name="attachment-demo_main" target="1.8" />
|
||||
<module name="attachment-demo_test" target="1.8" />
|
||||
@ -18,16 +16,11 @@
|
||||
<module name="confidential-identities_test" target="1.8" />
|
||||
<module name="corda-project_main" target="1.8" />
|
||||
<module name="corda-project_test" target="1.8" />
|
||||
<module name="corda-webserver_integrationTest" target="1.8" />
|
||||
<module name="corda-webserver_main" target="1.8" />
|
||||
<module name="corda-webserver_test" target="1.8" />
|
||||
<module name="cordapp_integrationTest" target="1.8" />
|
||||
<module name="cordapp_main" target="1.8" />
|
||||
<module name="cordapp_test" target="1.8" />
|
||||
<module name="cordform-common_main" target="1.8" />
|
||||
<module name="cordform-common_test" target="1.8" />
|
||||
<module name="cordformation_main" target="1.8" />
|
||||
<module name="cordformation_runnodes" target="1.8" />
|
||||
<module name="cordformation_test" target="1.8" />
|
||||
<module name="core_main" target="1.8" />
|
||||
<module name="core_test" target="1.8" />
|
||||
<module name="demobench_main" target="1.8" />
|
||||
@ -37,9 +30,6 @@
|
||||
<module name="docs_source_example-code_main" target="1.8" />
|
||||
<module name="docs_source_example-code_test" target="1.8" />
|
||||
<module name="docs_test" target="1.8" />
|
||||
<module name="doorman_integrationTest" target="1.8" />
|
||||
<module name="doorman_main" target="1.8" />
|
||||
<module name="doorman_test" target="1.8" />
|
||||
<module name="experimental_main" target="1.8" />
|
||||
<module name="experimental_test" target="1.8" />
|
||||
<module name="explorer-capsule_main" target="1.6" />
|
||||
@ -49,8 +39,6 @@
|
||||
<module name="finance_integrationTest" target="1.8" />
|
||||
<module name="finance_main" target="1.8" />
|
||||
<module name="finance_test" target="1.8" />
|
||||
<module name="gradle-plugins-cordform-common_main" target="1.8" />
|
||||
<module name="gradle-plugins-cordform-common_test" target="1.8" />
|
||||
<module name="graphs_main" target="1.8" />
|
||||
<module name="graphs_test" target="1.8" />
|
||||
<module name="intellij-plugin_main" target="1.8" />
|
||||
@ -91,12 +79,8 @@
|
||||
<module name="notary-demo_test" target="1.8" />
|
||||
<module name="perftestcordapp_main" target="1.8" />
|
||||
<module name="perftestcordapp_test" target="1.8" />
|
||||
<module name="publish-utils_main" target="1.8" />
|
||||
<module name="publish-utils_test" target="1.8" />
|
||||
<module name="quasar-hook_main" target="1.8" />
|
||||
<module name="quasar-hook_test" target="1.8" />
|
||||
<module name="quasar-utils_main" target="1.8" />
|
||||
<module name="quasar-utils_test" target="1.8" />
|
||||
<module name="rpc_integrationTest" target="1.8" />
|
||||
<module name="rpc_main" target="1.8" />
|
||||
<module name="rpc_smokeTest" target="1.8" />
|
||||
@ -107,15 +91,6 @@
|
||||
<module name="sandbox_test" target="1.8" />
|
||||
<module name="sgx-jvm_hsm-tool_main" target="1.8" />
|
||||
<module name="sgx-jvm_hsm-tool_test" target="1.8" />
|
||||
<module name="sgx-jvm_main" target="1.8" />
|
||||
<module name="sgx-jvm_sgx-signtool_main" target="1.8" />
|
||||
<module name="sgx-jvm_sgx-signtool_test" target="1.8" />
|
||||
<module name="sgx-jvm_test" target="1.8" />
|
||||
<module name="sgx-signtool_main" target="1.8" />
|
||||
<module name="sgx-signtool_test" target="1.8" />
|
||||
<module name="signing-server_integrationTest" target="1.8" />
|
||||
<module name="signing-server_main" target="1.8" />
|
||||
<module name="signing-server_test" target="1.8" />
|
||||
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
||||
<module name="simm-valuation-demo_main" target="1.8" />
|
||||
<module name="simm-valuation-demo_test" target="1.8" />
|
||||
@ -123,18 +98,8 @@
|
||||
<module name="smoke-test-utils_test" target="1.8" />
|
||||
<module name="test-common_main" target="1.8" />
|
||||
<module name="test-common_test" target="1.8" />
|
||||
<module name="test-utils_integrationTest" target="1.8" />
|
||||
<module name="test-utils_main" target="1.8" />
|
||||
<module name="test-utils_test" target="1.8" />
|
||||
<module name="testing-node-driver_integrationTest" target="1.8" />
|
||||
<module name="testing-node-driver_main" target="1.8" />
|
||||
<module name="testing-node-driver_test" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_main" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_test" target="1.8" />
|
||||
<module name="testing-test-common_main" target="1.8" />
|
||||
<module name="testing-test-common_test" target="1.8" />
|
||||
<module name="testing-test-utils_main" target="1.8" />
|
||||
<module name="testing-test-utils_test" target="1.8" />
|
||||
<module name="tools_main" target="1.8" />
|
||||
<module name="tools_test" target="1.8" />
|
||||
<module name="trader-demo_integrationTest" target="1.8" />
|
||||
@ -146,16 +111,13 @@
|
||||
<module name="verify-enclave_integrationTest" target="1.8" />
|
||||
<module name="verify-enclave_main" target="1.8" />
|
||||
<module name="verify-enclave_test" target="1.8" />
|
||||
<module name="web_main" target="1.8" />
|
||||
<module name="web_test" target="1.8" />
|
||||
<module name="webcapsule_main" target="1.6" />
|
||||
<module name="webcapsule_test" target="1.6" />
|
||||
<module name="webserver-webcapsule_main" target="1.6" />
|
||||
<module name="webserver-webcapsule_test" target="1.6" />
|
||||
<module name="webserver_integrationTest" target="1.8" />
|
||||
<module name="webserver_main" target="1.8" />
|
||||
<module name="webserver_test" target="1.8" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
|
||||
</component>
|
||||
</project>
|
@ -1,6 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="BankOfCordaDriverKt - Issue Web" type="JetRunConfigurationType" factoryName="Kotlin">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<extension name="krasa.grepconsole.plugin.runConfiguration.GrepRunConfigurationExtension" selectedProfileId="0" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.bank.BankOfCordaDriverKt" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--role ISSUE_CASH_WEB --quantity 100 --currency USD" />
|
||||
|
@ -1,6 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="BankOfCordaDriverKt - Run Stack" type="JetRunConfigurationType" factoryName="Kotlin">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<extension name="krasa.grepconsole.plugin.runConfiguration.GrepRunConfigurationExtension" selectedProfileId="0" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.bank.BankOfCordaDriverKt" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--role ISSUER" />
|
||||
|
@ -1,6 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Explorer - demo nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<extension name="krasa.grepconsole.plugin.runConfiguration.GrepRunConfigurationExtension" selectedProfileId="0" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
|
@ -1,6 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Explorer - demo nodes (simulation)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<extension name="krasa.grepconsole.plugin.runConfiguration.GrepRunConfigurationExtension" selectedProfileId="0" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="-S" />
|
||||
|
15
build.gradle
15
build.gradle
@ -22,19 +22,19 @@ buildscript {
|
||||
|
||||
ext.asm_version = '0.5.3'
|
||||
ext.artemis_version = '2.1.0'
|
||||
ext.jackson_version = '2.8.5'
|
||||
ext.jetty_version = '9.3.9.v20160517'
|
||||
ext.jackson_version = '2.9.2'
|
||||
ext.jetty_version = '9.4.7.v20170914'
|
||||
ext.jersey_version = '2.25'
|
||||
ext.jolokia_version = '2.0.0-M3'
|
||||
ext.assertj_version = '3.6.1'
|
||||
ext.assertj_version = '3.8.0'
|
||||
ext.slf4j_version = '1.7.25'
|
||||
ext.log4j_version = '2.7'
|
||||
ext.log4j_version = '2.9.1'
|
||||
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||
ext.guava_version = constants.getProperty("guavaVersion")
|
||||
ext.okhttp_version = '3.5.0'
|
||||
ext.netty_version = '4.1.9.Final'
|
||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||
ext.fileupload_version = '1.3.2'
|
||||
ext.fileupload_version = '1.3.3'
|
||||
ext.junit_version = '4.12'
|
||||
ext.mockito_version = '2.10.0'
|
||||
ext.jopt_simple_version = '5.0.2'
|
||||
@ -46,6 +46,8 @@ buildscript {
|
||||
ext.dokka_version = '0.9.14'
|
||||
ext.eddsa_version = '0.2.0'
|
||||
ext.dependency_checker_version = '3.0.1'
|
||||
ext.commons_collections_version = '4.1'
|
||||
ext.beanutils_version = '1.9.3'
|
||||
|
||||
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||
ext.java8_minUpdateVersion = '131'
|
||||
@ -196,9 +198,6 @@ if (!JavaVersion.current().java8Compatible)
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://dl.bintray.com/kotlin/exposed'
|
||||
}
|
||||
}
|
||||
|
||||
// Required for building out the fat JAR.
|
||||
|
@ -12,18 +12,22 @@ import net.corda.finance.USD
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class JacksonSupportTest : TestDependencyInjectionBase() {
|
||||
class JacksonSupportTest {
|
||||
companion object {
|
||||
private val SEED = BigInteger.valueOf(20170922L)
|
||||
val mapper = JacksonSupport.createNonRpcMapper()
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private lateinit var services: ServiceHub
|
||||
private lateinit var cordappProvider: CordappProvider
|
||||
|
||||
|
@ -37,6 +37,9 @@ dependencies {
|
||||
compile 'org.fxmisc.easybind:easybind:1.0.3'
|
||||
|
||||
// Artemis Client: ability to connect to an Artemis broker and control it.
|
||||
// TODO: remove the forced update of commons-collections and beanutils when artemis updates them
|
||||
compile "org.apache.commons:commons-collections4:${commons_collections_version}"
|
||||
compile "commons-beanutils:commons-beanutils:${beanutils_version}"
|
||||
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
|
||||
|
||||
// Unit testing helpers.
|
||||
|
@ -3,8 +3,12 @@ package net.corda.client.jfx.model
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.value.ObservableValue
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.finance.CHF
|
||||
import net.corda.finance.EUR
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.math.MathContext
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -24,9 +28,24 @@ abstract class ExchangeRate {
|
||||
/**
|
||||
* Default implementation of an exchange rate model, which uses a fixed exchange rate.
|
||||
*/
|
||||
private val usdExchangeRates: Map<Currency, BigDecimal> = mapOf(
|
||||
GBP to BigDecimal(1.31),
|
||||
EUR to BigDecimal(1.18),
|
||||
CHF to BigDecimal(1.01)
|
||||
)
|
||||
|
||||
private fun safeFetchRate(currency: Currency) =
|
||||
usdExchangeRates[currency] ?: throw IllegalArgumentException("No exchange rate for $currency")
|
||||
|
||||
// TODO hook up an actual oracle
|
||||
class ExchangeRateModel {
|
||||
val exchangeRate: ObservableValue<ExchangeRate> = SimpleObjectProperty<ExchangeRate>(object : ExchangeRate() {
|
||||
override fun rate(from: Currency, to: Currency) = BigDecimal.ONE
|
||||
override fun rate(from: Currency, to: Currency): BigDecimal =
|
||||
when {
|
||||
from == to -> BigDecimal.ONE
|
||||
USD == to -> safeFetchRate(from)
|
||||
USD == from -> BigDecimal.ONE.divide(safeFetchRate(to), MathContext.DECIMAL64)
|
||||
else -> safeFetchRate(from).divide(safeFetchRate(to), MathContext.DECIMAL64)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.finance.CHF
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.RUB
|
||||
import net.corda.finance.USD
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ExchangeRateModelTest {
|
||||
|
||||
companion object {
|
||||
private val instance = ExchangeRateModel().exchangeRate.value
|
||||
|
||||
private fun assertEquals(one: Amount<Currency>, another: Amount<Currency>) {
|
||||
assertEquals(one.token, another.token)
|
||||
assertTrue("$one != $another", {(one.toDecimal() - another.toDecimal()).abs() < BigDecimal(0.01) })
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun `perform fx testing`() {
|
||||
val tenSwissies = Amount(10, BigDecimal.ONE, CHF)
|
||||
assertEquals(instance.exchangeAmount(tenSwissies, CHF), tenSwissies)
|
||||
|
||||
val tenSwissiesInUsd = Amount(101, BigDecimal.ONE.divide(BigDecimal.TEN), USD)
|
||||
assertEquals(instance.exchangeAmount(tenSwissies, USD), tenSwissiesInUsd)
|
||||
|
||||
assertEquals(instance.exchangeAmount(tenSwissiesInUsd, CHF), tenSwissies)
|
||||
|
||||
val tenQuidInSwissies = Amount(1297, BigDecimal.ONE.divide(BigDecimal(100)), CHF)
|
||||
val tenQuid = Amount(10, BigDecimal.ONE, GBP)
|
||||
assertEquals(instance.exchangeAmount(tenQuid, CHF), tenQuidInSwissies)
|
||||
|
||||
assertEquals(instance.exchangeAmount(tenQuidInSwissies, GBP), tenQuid)
|
||||
|
||||
Assertions.assertThatThrownBy { instance.exchangeAmount(tenQuid, RUB) }.isInstanceOf(IllegalArgumentException::class.java)
|
||||
.hasMessage("No exchange rate for RUB")
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package net.corda.client.rpc;
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.core.contracts.Amount;
|
||||
import net.corda.core.messaging.CordaRPCOps;
|
||||
import net.corda.core.messaging.FlowHandle;
|
||||
@ -13,7 +12,7 @@ import net.corda.node.internal.Node;
|
||||
import net.corda.node.internal.StartedNode;
|
||||
import net.corda.nodeapi.User;
|
||||
import net.corda.testing.CoreTestUtils;
|
||||
import net.corda.testing.node.NodeBasedTest;
|
||||
import net.corda.testing.internal.NodeBasedTest;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -51,8 +50,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws ExecutionException, InterruptedException {
|
||||
CordaFuture<StartedNode<Node>> nodeFuture = startNotaryNode(getALICE().getName(), singletonList(rpcUser), true);
|
||||
node = nodeFuture.get();
|
||||
node = startNode(getALICE().getName(), 1, singletonList(rpcUser));
|
||||
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
|
||||
}
|
||||
|
||||
|
@ -2,89 +2,38 @@ package net.corda.client.rpc
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.esotericsoftware.kryo.KryoException
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.NodeBasedTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.driver.driver
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
|
||||
@CordaSerializable
|
||||
data class Packet(val x: () -> Long)
|
||||
|
||||
class BlacklistKotlinClosureTest : NodeBasedTest(listOf("net.corda.client.rpc")) {
|
||||
class BlacklistKotlinClosureTest {
|
||||
companion object {
|
||||
@Suppress("UNUSED") val logger = loggerFor<BlacklistKotlinClosureTest>()
|
||||
const val EVIL: Long = 666
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
@InitiatingFlow
|
||||
class FlowC(private val remoteParty: Party, private val data: Packet) : FlowLogic<Unit>() {
|
||||
class FlowC(@Suppress("unused") private val data: Packet) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val session = initiateFlow(remoteParty)
|
||||
val x = session.sendAndReceive<Packet>(data).unwrap { x -> x }
|
||||
logger.info("FlowC: ${x.x()}")
|
||||
}
|
||||
override fun call() = Unit
|
||||
}
|
||||
|
||||
@InitiatedBy(FlowC::class)
|
||||
class RemoteFlowC(private val session: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val packet = session.receive<Packet>().unwrap { x -> x }
|
||||
logger.info("RemoteFlowC: ${packet.x() + 1}")
|
||||
session.send(Packet({ packet.x() + 1 }))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
@Rule
|
||||
val expectedEx: ExpectedException = ExpectedException.none()
|
||||
|
||||
private val rpcUser = User("user1", "test", permissions = setOf("ALL"))
|
||||
private lateinit var aliceNode: StartedNode<Node>
|
||||
private lateinit var bobNode: StartedNode<Node>
|
||||
private lateinit var aliceClient: CordaRPCClient
|
||||
private var connection: CordaRPCConnection? = null
|
||||
|
||||
private fun login(username: String, password: String) {
|
||||
connection = aliceClient.start(username, password)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
aliceNode = startNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
|
||||
bobNode = startNode(BOB.name, rpcUsers = listOf(rpcUser)).getOrThrow()
|
||||
bobNode.registerInitiatedFlow(RemoteFlowC::class.java)
|
||||
aliceClient = CordaRPCClient(aliceNode.internals.configuration.rpcAddress!!)
|
||||
}
|
||||
|
||||
@After
|
||||
fun done() {
|
||||
connection?.close()
|
||||
bobNode.internals.stop()
|
||||
aliceNode.internals.stop()
|
||||
}
|
||||
@CordaSerializable
|
||||
data class Packet(val x: () -> Long)
|
||||
|
||||
@Test
|
||||
fun `closure sent via RPC`() {
|
||||
login(rpcUser.username, rpcUser.password)
|
||||
val proxy = connection!!.proxy
|
||||
expectedEx.expect(KryoException::class.java)
|
||||
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
||||
proxy.startFlow(::FlowC, bobNode.info.chooseIdentity(), Packet{ EVIL }).returnValue.getOrThrow()
|
||||
driver(startNodesInProcess = true) {
|
||||
val rpc = startNode(providedName = ALICE.name).getOrThrow().rpc
|
||||
val packet = Packet { EVIL }
|
||||
assertThatExceptionOfType(KryoException::class.java)
|
||||
.isThrownBy { rpc.startFlow(::FlowC, packet) }
|
||||
.withMessageContaining("is not annotated or on the whitelist, so cannot be used in serialization")
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.chooseIdentity
|
||||
import net.corda.testing.node.NodeBasedTest
|
||||
import net.corda.testing.internal.NodeBasedTest
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.After
|
||||
@ -49,7 +49,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
node = startNotaryNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
|
||||
node = startNode(ALICE.name, rpcUsers = listOf(rpcUser))
|
||||
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ class SwapIdentitiesFlowTests {
|
||||
val mockNet = MockNetwork(threadPerNode = true)
|
||||
|
||||
// Set up values we'll need
|
||||
val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name)
|
||||
val notaryNode = mockNet.createNotaryNode()
|
||||
val aliceNode = mockNet.createPartyNode(ALICE.name)
|
||||
val bobNode = mockNet.createPartyNode(BOB.name)
|
||||
val bob: Party = bobNode.services.myInfo.singleIdentity()
|
||||
@ -81,7 +81,7 @@ class SwapIdentitiesFlowTests {
|
||||
val mockNet = MockNetwork(threadPerNode = true)
|
||||
|
||||
// Set up values we'll need
|
||||
val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name)
|
||||
val notaryNode = mockNet.createNotaryNode()
|
||||
val aliceNode = mockNet.createPartyNode(ALICE.name)
|
||||
val bobNode = mockNet.createPartyNode(BOB.name)
|
||||
val bob: Party = bobNode.services.myInfo.singleIdentity()
|
||||
|
@ -15,4 +15,5 @@ import kotlin.reflect.KClass
|
||||
* @see InitiatingFlow
|
||||
*/
|
||||
@Target(CLASS)
|
||||
@MustBeDocumented
|
||||
annotation class InitiatedBy(val value: KClass<out FlowLogic<*>>)
|
@ -3,6 +3,7 @@ package net.corda.core.flows
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.internal.ResolveTransactionsFlow
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.unwrap
|
||||
import java.security.SignatureException
|
||||
@ -14,25 +15,41 @@ import java.security.SignatureException
|
||||
* [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing
|
||||
* attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify].
|
||||
*
|
||||
* @param otherSideSession session to the other side which is calling [SendTransactionFlow].
|
||||
* @param checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify].
|
||||
* Please note that it will *not* store the transaction to the vault unless that is explicitly requested.
|
||||
*
|
||||
* @property otherSideSession session to the other side which is calling [SendTransactionFlow].
|
||||
* @property checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify].
|
||||
* @property statesToRecord which transaction states should be recorded in the vault, if any.
|
||||
*/
|
||||
class ReceiveTransactionFlow(private val otherSideSession: FlowSession,
|
||||
private val checkSufficientSignatures: Boolean) : FlowLogic<SignedTransaction>() {
|
||||
/** Receives a [SignedTransaction] from [otherSideSession], verifies it and then records it in the vault. */
|
||||
constructor(otherSideSession: FlowSession) : this(otherSideSession, true)
|
||||
|
||||
class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
|
||||
private val checkSufficientSignatures: Boolean = true,
|
||||
private val statesToRecord: StatesToRecord = StatesToRecord.NONE) : FlowLogic<SignedTransaction>() {
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
@Suspendable
|
||||
@Throws(SignatureException::class,
|
||||
AttachmentResolutionException::class,
|
||||
TransactionResolutionException::class,
|
||||
TransactionVerificationException::class)
|
||||
override fun call(): SignedTransaction {
|
||||
return otherSideSession.receive<SignedTransaction>().unwrap {
|
||||
if (checkSufficientSignatures) {
|
||||
logger.trace("Receiving a transaction from ${otherSideSession.counterparty}")
|
||||
} else {
|
||||
logger.trace("Receiving a transaction (but without checking the signatures) from ${otherSideSession.counterparty}")
|
||||
}
|
||||
|
||||
val stx = otherSideSession.receive<SignedTransaction>().unwrap {
|
||||
subFlow(ResolveTransactionsFlow(it, otherSideSession))
|
||||
it.verify(serviceHub, checkSufficientSignatures)
|
||||
it
|
||||
}
|
||||
|
||||
if (checkSufficientSignatures) {
|
||||
// We should only send a transaction to the vault for processing if we did in fact fully verify it, and
|
||||
// there are no missing signatures. We don't want partly signed stuff in the vault.
|
||||
logger.trace("Successfully received fully signed tx ${stx.id}, sending to the vault for processing")
|
||||
serviceHub.recordTransactions(statesToRecord, setOf(stx))
|
||||
}
|
||||
return stx
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.exactAdd
|
||||
@ -94,7 +95,7 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
|
||||
// half way through, it's no big deal, although it might result in us attempting to re-download data
|
||||
// redundantly next time we attempt verification.
|
||||
it.verify(serviceHub)
|
||||
serviceHub.recordTransactions(false, it)
|
||||
serviceHub.recordTransactions(StatesToRecord.NONE, listOf(it))
|
||||
}
|
||||
|
||||
return signedTransaction?.let {
|
||||
|
@ -50,6 +50,26 @@ interface ServicesForResolution : StateLoader {
|
||||
val cordappProvider: CordappProvider
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls whether the transaction is sent to the vault at all, and if so whether states have to be relevant
|
||||
* or not in order to be recorded. Used in [ServiceHub.recordTransactions]
|
||||
*/
|
||||
enum class StatesToRecord {
|
||||
/** The received transaction is not sent to the vault at all. This is used within transaction resolution. */
|
||||
NONE,
|
||||
/**
|
||||
* All states that can be seen in the transaction will be recorded by the vault, even if none of the identities
|
||||
* on this node are a participant or owner.
|
||||
*/
|
||||
ALL_VISIBLE,
|
||||
/**
|
||||
* Only states that involve one of our public keys will be stored in the vault. This is the default. A public
|
||||
* key is involved (relevant) if it's in the [OwnableState.owner] field, or appears in the [ContractState.participants]
|
||||
* collection. This is usually equivalent to "can I change the contents of this state by signing a transaction".
|
||||
*/
|
||||
ONLY_RELEVANT
|
||||
}
|
||||
|
||||
/**
|
||||
* A service hub is the starting point for most operations you can do inside the node. You are provided with one
|
||||
* when a class annotated with [CordaService] is constructed, and you have access to one inside flows. Most RPCs
|
||||
@ -132,7 +152,9 @@ interface ServiceHub : ServicesForResolution {
|
||||
* @param txs The transactions to record.
|
||||
* @param notifyVault indicate if the vault should be notified for the update.
|
||||
*/
|
||||
fun recordTransactions(notifyVault: Boolean, txs: Iterable<SignedTransaction>)
|
||||
fun recordTransactions(notifyVault: Boolean, txs: Iterable<SignedTransaction>) {
|
||||
recordTransactions(if (notifyVault) StatesToRecord.ONLY_RELEVANT else StatesToRecord.NONE, txs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
|
||||
@ -142,12 +164,22 @@ interface ServiceHub : ServicesForResolution {
|
||||
recordTransactions(notifyVault, listOf(first, *remaining))
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
|
||||
* further processing if [statesToRecord] is not [StatesToRecord.NONE].
|
||||
* This is expected to be run within a database transaction.
|
||||
*
|
||||
* @param txs The transactions to record.
|
||||
* @param statesToRecord how the vault should treat the output states of the transaction.
|
||||
*/
|
||||
fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>)
|
||||
|
||||
/**
|
||||
* Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
|
||||
* further processing. This is expected to be run within a database transaction.
|
||||
*/
|
||||
fun recordTransactions(first: SignedTransaction, vararg remaining: SignedTransaction) {
|
||||
recordTransactions(true, first, *remaining)
|
||||
recordTransactions(listOf(first, *remaining))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,7 +187,7 @@ interface ServiceHub : ServicesForResolution {
|
||||
* further processing. This is expected to be run within a database transaction.
|
||||
*/
|
||||
fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||
recordTransactions(true, txs)
|
||||
recordTransactions(StatesToRecord.ONLY_RELEVANT, txs)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,7 +105,7 @@ object NodeInfoSchemaV1 : MappedSchema(
|
||||
private val persistentNodeInfos: Set<PersistentNodeInfo> = emptySet()
|
||||
) {
|
||||
constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false)
|
||||
: this(partyAndCert.party.name.toString(),
|
||||
: this(partyAndCert.name.toString(),
|
||||
partyAndCert.party.owningKey.toStringShort(),
|
||||
partyAndCert.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes, isMain)
|
||||
|
||||
|
@ -0,0 +1,93 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
/**
|
||||
* This annotation is used to mark an enumerated type as having had multiple members added, It acts
|
||||
* as a container annotation for instances of [CordaSerializationTransformEnumDefault], each of which
|
||||
* details individual additions.
|
||||
*
|
||||
* @property value an array of [CordaSerializationTransformEnumDefault].
|
||||
*
|
||||
* NOTE: Order is important, new values should always be added before any others
|
||||
*
|
||||
* ```
|
||||
* // initial implementation
|
||||
* enum class ExampleEnum {
|
||||
* A, B, C
|
||||
* }
|
||||
*
|
||||
* // First alteration
|
||||
* @CordaSerializationTransformEnumDefaults(
|
||||
* CordaSerializationTransformEnumDefault("D", "C"))
|
||||
* enum class ExampleEnum {
|
||||
* A, B, C, D
|
||||
* }
|
||||
*
|
||||
* // Second alteration, new transform is placed at the head of the list
|
||||
* @CordaSerializationTransformEnumDefaults(
|
||||
* CordaSerializationTransformEnumDefault("E", "C"),
|
||||
* CordaSerializationTransformEnumDefault("D", "C"))
|
||||
* enum class ExampleEnum {
|
||||
* A, B, C, D, E
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* IMPORTANT - Once added (and in production) do NOT remove old annotations. See documentation for
|
||||
* more discussion on this point!.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class CordaSerializationTransformEnumDefaults(vararg val value: CordaSerializationTransformEnumDefault)
|
||||
|
||||
/**
|
||||
* This annotation is used to mark an enumerated type as having had a new constant appended to it. For
|
||||
* each additional constant added a new annotation should be appended to the class. If more than one
|
||||
* is required the wrapper annotation [CordaSerializationTransformEnumDefaults] should be used to
|
||||
* encapsulate them
|
||||
*
|
||||
* @property new [String] equivalent of the value of the new constant
|
||||
* @property old [String] equivalent of the value of the existing constant that deserialisers should
|
||||
* favour when de-serialising a value they have no corresponding value for
|
||||
*
|
||||
* For Example
|
||||
*
|
||||
* Enum before modification:
|
||||
* ```
|
||||
* enum class ExampleEnum {
|
||||
* A, B, C
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Assuming at some point a new constant is added it is required we have some mechanism by which to tell
|
||||
* nodes with an older version of the class on their Class Path what to do if they attempt to deserialize
|
||||
* an example of the class with that new value
|
||||
*
|
||||
* ```
|
||||
* @CordaSerializationTransformEnumDefault("D", "C")
|
||||
* enum class ExampleEnum {
|
||||
* A, B, C, D
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* So, on deserialisation treat any instance of the enum that is encoded as D as C
|
||||
*
|
||||
* Adding a second new constant requires the wrapper annotation [CordaSerializationTransformEnumDefaults]
|
||||
*
|
||||
* ```
|
||||
* @CordaSerializationTransformEnumDefaults(
|
||||
* CordaSerializationTransformEnumDefault("E", "D"),
|
||||
* CordaSerializationTransformEnumDefault("D", "C"))
|
||||
* enum class ExampleEnum {
|
||||
* A, B, C, D, E
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* It's fine to assign the second new value a default that may not be present in all versions as in this
|
||||
* case it will work down the transform hierarchy until it finds a value it can apply, in this case it would
|
||||
* try E -> D -> C (when E -> D fails)
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// When Kotlin starts writing 1.8 class files enable this, it removes the need for the wrapping annotation
|
||||
//@Repeatable
|
||||
annotation class CordaSerializationTransformEnumDefault(val new: String, val old: String)
|
||||
|
@ -0,0 +1,35 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
/**
|
||||
* This annotation is used to mark a class as having had multiple elements renamed as a container annotation for
|
||||
* instances of [CordaSerializationTransformRename], each of which details an individual rename.
|
||||
*
|
||||
* @property value an array of [CordaSerializationTransformRename]
|
||||
*
|
||||
* NOTE: Order is important, new values should always be added before existing
|
||||
*
|
||||
* IMPORTANT - Once added (and in production) do NOT remove old annotations. See documentation for
|
||||
* more discussion on this point!.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class CordaSerializationTransformRenames(vararg val value: CordaSerializationTransformRename)
|
||||
|
||||
// TODO When we have class renaming update the docs
|
||||
/**
|
||||
* This annotation is used to mark a class has having had a property element. It is used by the
|
||||
* AMQP deserialiser to allow instances with different versions of the class on their Class Path
|
||||
* to successfully deserialize the object
|
||||
*
|
||||
* NOTE: Renaming of the class itself is not be done with this annotation. For class renaming
|
||||
* see ???
|
||||
*
|
||||
* @property to [String] representation of the properties new name
|
||||
* @property from [String] representation of the properties old new
|
||||
*
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// When Kotlin starts writing 1.8 class files enable this, it removes the need for the wrapping annotation
|
||||
//@Repeatable
|
||||
annotation class CordaSerializationTransformRename(val to: String, val from: String)
|
@ -3,6 +3,7 @@ package net.corda.core.serialization
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.WriteOnceProperty
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.sequence
|
||||
@ -172,13 +173,13 @@ interface SerializationContext {
|
||||
/**
|
||||
* Global singletons to be used as defaults that are injected elsewhere (generally, in the node or in RPC client).
|
||||
*/
|
||||
object SerializationDefaults {
|
||||
var SERIALIZATION_FACTORY: SerializationFactory by WriteOnceProperty()
|
||||
var P2P_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
var RPC_SERVER_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
var RPC_CLIENT_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
var STORAGE_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
var CHECKPOINT_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
object SerializationDefaults : SerializationEnvironment {
|
||||
override var SERIALIZATION_FACTORY: SerializationFactory by WriteOnceProperty()
|
||||
override var P2P_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
override var RPC_SERVER_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
override var RPC_CLIENT_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
override var STORAGE_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
override var CHECKPOINT_CONTEXT: SerializationContext by WriteOnceProperty()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,13 @@
|
||||
package net.corda.core.serialization.internal
|
||||
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
|
||||
interface SerializationEnvironment {
|
||||
val SERIALIZATION_FACTORY: SerializationFactory
|
||||
val P2P_CONTEXT: SerializationContext
|
||||
val RPC_SERVER_CONTEXT: SerializationContext
|
||||
val RPC_CLIENT_CONTEXT: SerializationContext
|
||||
val STORAGE_CONTEXT: SerializationContext
|
||||
val CHECKPOINT_CONTEXT: SerializationContext
|
||||
}
|
@ -8,13 +8,16 @@ import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
import java.util.function.Predicate
|
||||
import kotlin.test.*
|
||||
|
||||
class CompatibleTransactionTests : TestDependencyInjectionBase() {
|
||||
|
||||
class CompatibleTransactionTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val dummyOutState = TransactionState(DummyState(0), DummyContract.PROGRAM_ID, DUMMY_NOTARY)
|
||||
private val stateRef1 = StateRef(SecureHash.randomSHA256(), 0)
|
||||
private val stateRef2 = StateRef(SecureHash.randomSHA256(), 1)
|
||||
|
@ -4,10 +4,11 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.UpgradeCommand
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyContractV2
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
@ -15,7 +16,11 @@ import kotlin.test.assertTrue
|
||||
/**
|
||||
* Tests for the version 2 dummy contract, to cover ensuring upgrade transactions are built correctly.
|
||||
*/
|
||||
class DummyContractV2Tests : TestDependencyInjectionBase() {
|
||||
class DummyContractV2Tests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `upgrade from v1`() {
|
||||
val services = MockServices()
|
||||
|
@ -3,21 +3,21 @@ package net.corda.core.contracts
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.chooseIdentity
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.dummyCommand
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.function.Predicate
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class LedgerTransactionQueryTests : TestDependencyInjectionBase() {
|
||||
|
||||
class LedgerTransactionQueryTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val services: MockServices = MockServices()
|
||||
|
||||
@Before
|
||||
|
@ -10,14 +10,18 @@ import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.MockAttachment
|
||||
import net.corda.testing.node.MockAttachmentStorage
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.security.KeyPair
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
class TransactionTests : TestDependencyInjectionBase() {
|
||||
class TransactionTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private fun makeSigned(wtx: WireTransaction, vararg keys: KeyPair, notarySig: Boolean = true): SignedTransaction {
|
||||
val keySigs = keys.map { it.sign(SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(it.public).schemeNumberID))) }
|
||||
val sigs = if (notarySig) {
|
||||
|
@ -9,8 +9,8 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -20,7 +20,10 @@ import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CompositeKeyTests : TestDependencyInjectionBase() {
|
||||
class CompositeKeyTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder: TemporaryFolder = TemporaryFolder()
|
||||
|
@ -10,6 +10,7 @@ import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.testing.*
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.util.function.Predicate
|
||||
@ -17,14 +18,14 @@ import java.util.stream.IntStream
|
||||
import kotlin.streams.toList
|
||||
import kotlin.test.*
|
||||
|
||||
class PartialMerkleTreeTest : TestDependencyInjectionBase() {
|
||||
class PartialMerkleTreeTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val nodes = "abcdef"
|
||||
private val hashed = nodes.map {
|
||||
initialiseTestSerialization()
|
||||
try {
|
||||
it.serialize().sha256()
|
||||
} finally {
|
||||
resetTestSerialization()
|
||||
private val hashed = nodes.map { node ->
|
||||
withTestSerialization {
|
||||
node.serialize().sha256()
|
||||
}
|
||||
}
|
||||
private val expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
||||
|
@ -2,13 +2,18 @@ package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.security.SignatureException
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SignedDataTest : TestDependencyInjectionBase() {
|
||||
class SignedDataTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Before
|
||||
fun initialise() {
|
||||
serialized = data.serialize()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.security.SignatureException
|
||||
import kotlin.test.assertTrue
|
||||
@ -8,8 +9,10 @@ import kotlin.test.assertTrue
|
||||
/**
|
||||
* Digital signature MetaData tests.
|
||||
*/
|
||||
class TransactionSignatureTest : TestDependencyInjectionBase() {
|
||||
|
||||
class TransactionSignatureTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
val testBytes = "12345678901234567890123456789012".toByteArray()
|
||||
|
||||
/** Valid sign and verify. */
|
||||
|
@ -6,47 +6,48 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.utilities.KEYSTORE_TYPE
|
||||
import net.corda.node.utilities.save
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.getTestPartyAndCertificate
|
||||
import net.corda.testing.withTestSerialization
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyStore
|
||||
|
||||
class PartyAndCertificateTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `kryo serialisation`() {
|
||||
withTestSerialization {
|
||||
val original = getTestPartyAndCertificate(Party(
|
||||
CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"),
|
||||
entropyToKeyPair(BigInteger.valueOf(83)).public))
|
||||
val copy = original.serialize().deserialize()
|
||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||
assertThat(copy.certPath).isEqualTo(original.certPath)
|
||||
assertThat(copy.certificate).isEqualTo(original.certificate)
|
||||
}
|
||||
val original = getTestPartyAndCertificate(Party(
|
||||
CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"),
|
||||
entropyToKeyPair(BigInteger.valueOf(83)).public))
|
||||
val copy = original.serialize().deserialize()
|
||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||
assertThat(copy.certPath).isEqualTo(original.certPath)
|
||||
assertThat(copy.certificate).isEqualTo(original.certificate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jdk serialization`() {
|
||||
withTestSerialization {
|
||||
val identity = getTestPartyAndCertificate(Party(
|
||||
CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"),
|
||||
entropyToKeyPair(BigInteger.valueOf(83)).public))
|
||||
val original = identity.certificate
|
||||
val storePassword = "test"
|
||||
val keyStoreFilePath = File.createTempFile("serialization_test", "jks").toPath()
|
||||
var keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
keyStore.load(null, storePassword.toCharArray())
|
||||
keyStore.setCertificateEntry(identity.name.toString(), original)
|
||||
keyStore.save(keyStoreFilePath, storePassword)
|
||||
val identity = getTestPartyAndCertificate(Party(
|
||||
CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"),
|
||||
entropyToKeyPair(BigInteger.valueOf(83)).public))
|
||||
val original = identity.certificate
|
||||
val storePassword = "test"
|
||||
val keyStoreFilePath = File.createTempFile("serialization_test", "jks").toPath()
|
||||
var keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
keyStore.load(null, storePassword.toCharArray())
|
||||
keyStore.setCertificateEntry(identity.name.toString(), original)
|
||||
keyStore.save(keyStoreFilePath, storePassword)
|
||||
|
||||
// Load the key store back in again
|
||||
keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
keyStoreFilePath.read { keyStore.load(it, storePassword.toCharArray()) }
|
||||
val copy = keyStore.getCertificate(identity.name.toString())
|
||||
assertThat(copy).isEqualTo(original) // .isNotSameAs(original)
|
||||
}
|
||||
// Load the key store back in again
|
||||
keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
keyStoreFilePath.read { keyStore.load(it, storePassword.toCharArray()) }
|
||||
val copy = keyStore.getCertificate(identity.name.toString())
|
||||
assertThat(copy).isEqualTo(original) // .isNotSameAs(original)
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,15 @@ package net.corda.core.serialization
|
||||
|
||||
import net.corda.finance.contracts.CommercialPaper
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CommandsSerializationTests : TestDependencyInjectionBase() {
|
||||
class CommandsSerializationTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `test cash move serialization`() {
|
||||
|
@ -9,6 +9,7 @@ import net.corda.finance.POUNDS
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.security.SignatureException
|
||||
import java.util.*
|
||||
@ -16,7 +17,10 @@ import kotlin.reflect.jvm.javaField
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class TransactionSerializationTests : TestDependencyInjectionBase() {
|
||||
class TransactionSerializationTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val TEST_CASH_PROGRAM_ID = "net.corda.core.serialization.TransactionSerializationTests\$TestCash"
|
||||
|
||||
class TestCash : Contract {
|
||||
|
@ -5,11 +5,15 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.UniquenessException
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.testing.DUMMY_PARTY
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class UniquenessExceptionSerializationTest : TestDependencyInjectionBase() {
|
||||
class UniquenessExceptionSerializationTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun testSerializationRoundTrip() {
|
||||
|
@ -7,13 +7,16 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.serialization.KRYO_CHECKPOINT_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
|
||||
class KotlinUtilsTest : TestDependencyInjectionBase() {
|
||||
class KotlinUtilsTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
@JvmField
|
||||
@Rule
|
||||
val expectedEx: ExpectedException = ExpectedException.none()
|
||||
|
@ -7,8 +7,7 @@ import com.google.common.collect.testing.features.CollectionSize
|
||||
import junit.framework.TestSuite
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.testing.initialiseTestSerialization
|
||||
import net.corda.testing.resetTestSerialization
|
||||
import net.corda.testing.withTestSerialization
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Test
|
||||
@ -49,14 +48,10 @@ class NonEmptySetTest {
|
||||
|
||||
@Test
|
||||
fun `serialize deserialize`() {
|
||||
initialiseTestSerialization()
|
||||
try {
|
||||
withTestSerialization {
|
||||
val original = NonEmptySet.of(-17, 22, 17)
|
||||
val copy = original.serialize().deserialize()
|
||||
|
||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||
} finally {
|
||||
resetTestSerialization()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@
|
||||
<br>
|
||||
API reference: <a href="api/kotlin/corda/index.html">Kotlin</a>/ <a href="api/javadoc/index.html">JavaDoc</a>
|
||||
<br>
|
||||
<a href="https://discourse.corda.net">Discourse Forums</a>
|
||||
<br>
|
||||
<a href="http://slack.corda.net">Slack</a>
|
||||
<br>
|
||||
{% endblock %}
|
||||
<a href="https://stackoverflow.com/questions/tagged/corda">Stack Overflow</a>
|
||||
<br>
|
||||
{% endblock %}
|
@ -6,10 +6,10 @@
|
||||
<br>
|
||||
API reference: <a href="api/kotlin/corda/index.html">Kotlin</a>/ <a href="api/javadoc/index.html">JavaDoc</a>
|
||||
<br>
|
||||
<a href="https://discourse.corda.net">Discourse Forums</a>
|
||||
<br>
|
||||
<a href="http://slack.corda.net">Slack</a>
|
||||
<br>
|
||||
<a href="https://stackoverflow.com/questions/tagged/corda">Stack Overflow</a>
|
||||
<br>
|
||||
<select id="versionDropdown" class="version-dropdown" onChange="window.location.href=this.value"></select>
|
||||
<br>
|
||||
<span style="display:none" id="version">{{ version }}</span>
|
||||
|
@ -7,61 +7,59 @@ API: Identity
|
||||
|
||||
Party
|
||||
-----
|
||||
Identities on the network are represented by ``AbstractParty``. There are two types of ``AbstractParty``:
|
||||
Parties on the network are represented using the ``AbstractParty`` class. There are two types of ``AbstractParty``:
|
||||
|
||||
* ``Party``, identified by a ``PublicKey`` and a ``CordaX500Name``
|
||||
* ``AnonymousParty``, identified by a ``PublicKey`` only
|
||||
|
||||
* ``AnonymousParty``, identified by a ``PublicKey``
|
||||
Using ``AnonymousParty`` to identify parties in states and commands prevents nodes from learning the identities
|
||||
of the parties involved in a transaction when they verify the transaction's dependency chain. When preserving the
|
||||
anonymity of each party is not required (e.g. for internal processing), ``Party`` can be used instead.
|
||||
|
||||
For example, in a transaction sent to your node as part of a chain of custody it is important you can convince yourself
|
||||
of the transaction's validity, but equally important that you don't learn anything about who was involved in that
|
||||
transaction. In these cases ``AnonymousParty`` should be used by flows constructing when transaction states and commands.
|
||||
In contrast, for internal processing where extended details of a party are required, the ``Party`` class should be used
|
||||
instead. The identity service provides functionality for flows to resolve anonymous parties to full parties, dependent
|
||||
on the anonymous party's identity having been registered with the node earlier (typically this is handled by
|
||||
``SwapIdentitiesFlow`` or ``IdentitySyncFlow``, discussed below).
|
||||
The identity service allows flows to resolve ``AnonymousParty`` to ``Party``, but only if the anonymous party's
|
||||
identity has already been registered with the node (typically handled by ``SwapIdentitiesFlow`` or
|
||||
``IdentitySyncFlow``, discussed below).
|
||||
|
||||
Party names are held within the ``CordaX500Name`` data class, which enforces the structure of names within Corda, as
|
||||
well as ensuring a consistent rendering of the names in plain text.
|
||||
Party names use the ``CordaX500Name`` data class, which enforces the structure of names within Corda, as well as
|
||||
ensuring a consistent rendering of the names in plain text.
|
||||
|
||||
The support for both Party and AnonymousParty classes in Corda enables sophisticated selective disclosure of identity
|
||||
information. For example, it is possible to construct a Transaction using an AnonymousParty, so nobody can learn of your
|
||||
involvement by inspection of the transaction, yet prove to specific counterparts that this AnonymousParty actually is
|
||||
owned by your well known identity. This disclosure is achieved through the use of the PartyAndCertificate data class
|
||||
which can be propagated to those who need to know, and contains the Party's X.509 certificate path to provide proof of
|
||||
ownership by a well known identity.
|
||||
Support for both ``Party`` and ``AnonymousParty`` classes in Corda enables sophisticated selective disclosure of
|
||||
identity information. For example, it is possible to construct a transaction using an ``AnonymousParty`` (so nobody can
|
||||
learn of your involvement by inspection of the transaction), yet prove to specific counterparts that this
|
||||
``AnonymousParty`` actually corresponds to your well-known identity. This is achieved using the
|
||||
``PartyAndCertificate`` data class, which contains the X.509 certificate path proving that a given ``AnonymousParty``
|
||||
corresponds to a given ``Party``. Each ``PartyAndCertificate`` can be propagated to counterparties on a need-to-know
|
||||
basis.
|
||||
|
||||
The PartyAndCertificate class is also used in the network map service to represent well known identities, in which
|
||||
scenario the certificate path proves its issuance by the Doorman service.
|
||||
The ``PartyAndCertificate`` class is also used by the network map service to represent well-known identities, with the
|
||||
certificate path proving the certificate was issued by the doorman service.
|
||||
|
||||
|
||||
Confidential Identities
|
||||
Confidential identities
|
||||
-----------------------
|
||||
|
||||
Confidential identities are key pairs where the corresponding X.509 certificate (and path) are not made public, so that parties who
|
||||
are not involved in the transaction cannot identify its participants. They are owned by a well known identity, which
|
||||
must sign the X.509 certificate. Before constructing a new transaction the involved parties must generate and send new
|
||||
confidential identities to each other, a process which is managed using ``SwapIdentitiesFlow`` (discussed below). The
|
||||
public keys of these confidential identities are then used when generating output states and commands for the transaction.
|
||||
Confidential identities are key pairs where the corresponding X.509 certificate (and path) are not made public, so that
|
||||
parties who are not involved in the transaction cannot identify the owner. They are owned by a well-known identity,
|
||||
which must sign the X.509 certificate. Before constructing a new transaction the involved parties must generate and
|
||||
exchange new confidential identities, a process which is managed using ``SwapIdentitiesFlow`` (discussed below). The
|
||||
public keys of these confidential identities are then used when generating output states and commands for the
|
||||
transaction.
|
||||
|
||||
Where using outputs from a previous transaction in a new transaction, counterparties may need to know who the involved
|
||||
parties are. One example is in ``TwoPartyTradeFlow`` which delegates to ``CollectSignaturesFlow`` to gather certificates
|
||||
from both parties. ``CollectSignaturesFlow`` requires that a confidential identity of the initiating node has signed
|
||||
the transaction, and verifying this requires the receiving node has a copy of the confidential identity for the input
|
||||
state. ``IdentitySyncFlow`` can be used to synchronize the confidential identities we have the certificate paths for, in
|
||||
a single transaction, to another node.
|
||||
parties are. One example is the ``TwoPartyTradeFlow``, where an existing asset is exchanged for cash. If confidential
|
||||
identities are being used, the buyer will want to ensure that the asset being transferred is owned by the seller, and
|
||||
the seller will likewise want to ensure that the cash being transferred is owned by the buyer. Verifying this requires
|
||||
both nodes to have a copy of the confidential identities for the asset and cash input states. ``IdentitySyncFlow``
|
||||
manages this process. It takes as inputs a transaction and a counterparty, and for every confidential identity involved
|
||||
in that transaction for which the calling node holds the certificate path, it sends this certificate path to the
|
||||
counterparty.
|
||||
|
||||
.. note:: ``CollectSignaturesFlow`` requires that the initiating node has signed the transaction, and as such all nodes
|
||||
providing signatures must recognise the signing key used by the initiating node as being either its well known identity
|
||||
or a confidential identity they have the certificate for.
|
||||
SwapIdentitiesFlow
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
``SwapIdentitiesFlow`` is typically run as a subflow of another flow. It takes as its sole constructor argument the
|
||||
counterparty we want to exchange confidential identities with. It returns a mapping from the identities of the caller
|
||||
and the counterparty to their new confidential identities. In the future, this flow will be extended to handle swapping
|
||||
identities with multiple parties at once.
|
||||
|
||||
Swap identities flow
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``SwapIdentitiesFlow`` takes the party to swap identities with in its constructor (the counterparty), and is typically run as a subflow of
|
||||
another flow. It returns a mapping from well known identities of the calling flow and our counterparty to the new
|
||||
confidential identities; in future this will be extended to handle swapping identities with multiple parties.
|
||||
You can see an example of it being used in ``TwoPartyDealFlow.kt``:
|
||||
You can see an example of using ``SwapIdentitiesFlow`` in ``TwoPartyDealFlow.kt``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -71,30 +69,35 @@ You can see an example of it being used in ``TwoPartyDealFlow.kt``:
|
||||
:end-before: DOCEND 2
|
||||
:dedent: 8
|
||||
|
||||
The swap identities flow goes through the following key steps:
|
||||
``SwapIdentitiesFlow`` goes through the following key steps:
|
||||
|
||||
1. Generate a nonce value to form a challenge to the other nodes.
|
||||
2. Send nonce value to all counterparties, and receive their nonce values.
|
||||
3. Generate a new confidential identity from our well known identity.
|
||||
1. Generate a nonce value to form a challenge to the other nodes
|
||||
2. Send nonce value to all counterparties, and receive their nonce values
|
||||
3. Generate a new confidential identity from our well-known identity
|
||||
4. Create a data blob containing the new confidential identity (public key, name and X.509 certificate path),
|
||||
and the hash of the nonce values.
|
||||
5. Sign the resulting data blob with the confidential identity's private key.
|
||||
6. Send the confidential identity and data blob signature to all counterparties, while receiving theirs.
|
||||
7. Verify the signatures to ensure that identities were generated by the involved set of parties.
|
||||
8. Verify the confidential identities are owned by the expected well known identities.
|
||||
9. Store the confidential identities and return them to the calling flow.
|
||||
and the hash of the nonce values
|
||||
5. Sign the resulting data blob with the confidential identity's private key
|
||||
6. Send the confidential identity and data blob signature to all counterparties, while receiving theirs
|
||||
7. Verify the signatures to ensure that identities were generated by the involved set of parties
|
||||
8. Verify the confidential identities are owned by the expected well known identities
|
||||
9. Store the confidential identities and return them to the calling flow
|
||||
|
||||
This ensures not only that the confidential identity X.509 certificates are signed by the correct well known identities,
|
||||
but also that the confidential identity private key is held by the counterparty, and that a party cannot claim ownership
|
||||
another party's confidential identities belong to its well known identity.
|
||||
This ensures not only that the confidential identity X.509 certificates are signed by the correct well-known
|
||||
identities, but also that the confidential identity private key is held by the counterparty, and that a party cannot
|
||||
claim ownership of another party's confidential identities.
|
||||
|
||||
Identity synchronization flow
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
IdentitySyncFlow
|
||||
~~~~~~~~~~~~~~~~
|
||||
When constructing a transaction whose input states reference confidential identities, it is common for counterparties
|
||||
to require knowledge of which well-known identity each confidential identity maps to. ``IdentitySyncFlow`` handles this
|
||||
process. You can see an example of its use in ``TwoPartyTradeFlow.kt``.
|
||||
|
||||
When constructing a transaction whose input states reference confidential identities, it is common for other signing
|
||||
entities (counterparties) to require to know which well known identities those confidential identities map to. The
|
||||
``IdentitySyncFlow`` handles distribution of a node's confidential identities, and you can see an example of its
|
||||
use in ``TwoPartyTradeFlow.kt``:
|
||||
``IdentitySyncFlow`` is divided into two parts:
|
||||
|
||||
* ``IdentitySyncFlow.Send``
|
||||
* ``IdentitySyncFlow.Receive``
|
||||
|
||||
``IdentitySyncFlow.Send`` is invoked by the party initiating the identity synchronization:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -106,33 +109,40 @@ use in ``TwoPartyTradeFlow.kt``:
|
||||
|
||||
The identity synchronization flow goes through the following key steps:
|
||||
|
||||
1. Extract participant identities from all input and output states. Filter this set down to confidential identities
|
||||
of the flow's well known identity. Required signers on commands are currently ignored as they are presumed to be
|
||||
included in the participants on states, or to be well known identities of services (such as an oracle service).
|
||||
1. Extract participant identities from all input and output states and remove any well known identities. Required
|
||||
signers on commands are currently ignored as they are presumed to be included in the participants on states, or to
|
||||
be well-known identities of services (such as an oracle service)
|
||||
2. For each counterparty node, send a list of the public keys of the confidential identities, and receive back a list
|
||||
of those the counterparty needs the certificate path for.
|
||||
3. Verify the requested list of identities contains only confidential identities in the offered list, and abort otherwise.
|
||||
4. Send the requested confidential identities as ``PartyAndCertificate`` instances to the counterparty.
|
||||
of those the counterparty needs the certificate path for
|
||||
3. Verify the requested list of identities contains only confidential identities in the offered list, and abort
|
||||
otherwise
|
||||
4. Send the requested confidential identities as ``PartyAndCertificate`` instances to the counterparty
|
||||
|
||||
.. note:: ``IdentitySyncFlow`` works on a push basis. Receiving nodes can only request confidential identities being
|
||||
offered by the initiating node. There is no standard flow for nodes to collect
|
||||
confidential identities before assembling a transaction, and this is left for individual flows to manage if required.
|
||||
.. note:: ``IdentitySyncFlow`` works on a push basis. The initiating node can only send confidential identities it has
|
||||
the X.509 certificates for, and the remote nodes can only request confidential identities being offered (are
|
||||
referenced in the transaction passed to the initiating flow). There is no standard flow for nodes to collect
|
||||
confidential identities before assembling a transaction, and this is left for individual flows to manage if
|
||||
required.
|
||||
|
||||
``IdentitySyncFlow`` will serve only confidential identities in the provided transaction, limited to those that are
|
||||
signed by the well known identity the flow is initiated by. This is done to avoid a risk of a node including
|
||||
states it doesn't have the well known identity of participants in, to try convincing one of its counterparties to
|
||||
reveal the identity. In case of a more complex transaction where multiple well known identities need confidential
|
||||
identities distributed this flow should be run by each node in turn. For example:
|
||||
Meanwhile, ``IdentitySyncFlow.Receive`` is invoked by all the other (non-initiating) parties involved in the identity
|
||||
synchronization process:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 07
|
||||
:end-before: DOCEND 07
|
||||
:dedent: 12
|
||||
|
||||
``IdentitySyncFlow`` will serve all confidential identities in the provided transaction, irrespective of well-known
|
||||
identity. This is important for more complex transaction cases with 3+ parties, for example:
|
||||
|
||||
* Alice is building the transaction, and provides some input state *x* owned by a confidential identity of Alice
|
||||
* Bob provides some input state *y* owned by a confidential identity of Bob
|
||||
* Charlie provides some input state *z* owned by a confidential identity of Charlie
|
||||
|
||||
Alice, Bob and Charlie must all run ``IdentitySyncFlow`` to send their involved confidential identities to the other
|
||||
parties. For an illustration of the security implications of not requiring this, consider:
|
||||
|
||||
1. Alice is building the transaction, and provides some input state *x* owned by a confidential identity of Alice
|
||||
2. Bob provides some input state *y* owned by a confidential identity it doesn't know the well known identity of, but
|
||||
Alice does.
|
||||
3. Alice runs ``IdentitySyncFlow`` and sends not just their confidential identity, but also the confidential identity
|
||||
in state *y*, violating the privacy model.
|
||||
Alice may know all of the confidential identities ahead of time, but Bob not know about Charlie's and vice-versa.
|
||||
The assembled transaction therefore has three input states *x*, *y* and *z*, for which only Alice possesses
|
||||
certificates for all confidential identities. ``IdentitySyncFlow`` must send not just Alice's confidential identity but
|
||||
also any other identities in the transaction to the Bob and Charlie.
|
@ -39,17 +39,17 @@ UNRELEASED
|
||||
``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` have also been removed and replaced by a single
|
||||
``notary`` config object. See :doc:`corda-configuration-file` for more details.
|
||||
|
||||
* Gradle task ``deployNodes`` can have an additional parameter `configFile` with the path to a properties file
|
||||
* Gradle task ``deployNodes`` can have an additional parameter ``configFile`` with the path to a properties file
|
||||
to be appended to node.conf.
|
||||
|
||||
* Cordformation node building DSL can have an additional parameter `configFile` with the path to a properties file
|
||||
* Cordformation node building DSL can have an additional parameter ``configFile`` with the path to a properties file
|
||||
to be appended to node.conf.
|
||||
|
||||
* ``FlowLogic`` now has a static method called ``sleep`` which can be used in certain circumstances to help with resolving
|
||||
contention over states in flows. This should be used in place of any other sleep primitive since these are not compatible
|
||||
with flows and their use will be prevented at some point in the future. Pay attention to the warnings and limitations
|
||||
described in the documentation for this method. This helps resolve a bug in ``Cash`` coin selection.
|
||||
A new static property `currentTopLevel` returns the top most `FlowLogic` instance, or null if not in a flow.
|
||||
A new static property ``currentTopLevel`` returns the top most ``FlowLogic`` instance, or null if not in a flow.
|
||||
|
||||
* ``CordaService`` annotated classes should be upgraded to take a constructor parameter of type ``AppServiceHub`` which
|
||||
allows services to start flows marked with the ``StartableByService`` annotation. For backwards compatability
|
||||
@ -67,7 +67,10 @@ UNRELEASED
|
||||
* A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check
|
||||
if every command that a signer should receive (e.g. an Oracle) is indeed visible.
|
||||
|
||||
* Change the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder.
|
||||
* Changed the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder.
|
||||
|
||||
* The ``ReceiveTransactionFlow`` can now be told to record the transaction at the same time as receiving it. Using this
|
||||
feature, better support for observer/regulator nodes has been added. See :doc:`tutorial-observer-nodes`.
|
||||
|
||||
.. _changelog_v1:
|
||||
|
||||
@ -372,7 +375,7 @@ Milestone 14
|
||||
use to exclude core Corda JARs from being built into Cordapp fat JARs.
|
||||
|
||||
* ``database`` field in ``AbstractNode`` class has changed the type from ``org.jetbrains.exposed.sql.Database`` to
|
||||
‘net.corda.node.utilities.CordaPersistence’ - no change is needed for the typical use
|
||||
???net.corda.node.utilities.CordaPersistence??? - no change is needed for the typical use
|
||||
(i.e. services.database.transaction { code block } ) however a change is required when Database was explicitly declared
|
||||
|
||||
* ``DigitalSignature.LegallyIdentifiable``, previously used to identify a signer (e.g. in Oracles), has been removed.
|
||||
|
@ -118,13 +118,6 @@ path to the node's base directory.
|
||||
|
||||
Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified.
|
||||
|
||||
:networkMapService: If `null`, or missing the node is declaring itself as the NetworkMapService host. Otherwise this is
|
||||
a config object with the details of the network map service:
|
||||
|
||||
:address: Host and port string of the ArtemisMQ broker hosting the network map node
|
||||
:legalName: Legal name of the node. This is required as part of the TLS host verification process. The node will
|
||||
reject the connection to the network map service if it provides a TLS common name which doesn't match with this value.
|
||||
|
||||
:minimumPlatformVersion: Used by the node if it's running the network map service to enforce a minimum version requirement
|
||||
on registrations - any node on a Platform Version lower than this value will have their registration rejected.
|
||||
Defaults to 1 if absent.
|
||||
|
@ -35,7 +35,7 @@ class CustomVaultQueryTest {
|
||||
"net.corda.docs"
|
||||
)
|
||||
)
|
||||
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
|
||||
mockNet.createNotaryNode()
|
||||
nodeA = mockNet.createPartyNode()
|
||||
nodeB = mockNet.createPartyNode()
|
||||
nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.nodeapi.config.parseAs
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.verifier.Verifier
|
||||
import org.junit.Test
|
||||
import java.nio.file.Path
|
||||
@ -34,7 +33,7 @@ class ExampleConfigTest {
|
||||
ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory,
|
||||
configFile = it
|
||||
).parseAs<FullNodeConfiguration>()
|
||||
).parseAsNodeConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ class FxTransactionBuildTutorialTest {
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName))
|
||||
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
|
||||
mockNet.createNotaryNode()
|
||||
nodeA = mockNet.createPartyNode()
|
||||
nodeB = mockNet.createPartyNode()
|
||||
nodeB.internals.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java)
|
||||
|
@ -34,7 +34,7 @@ class WorkflowTransactionBuildTutorialTest {
|
||||
fun setup() {
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.docs"))
|
||||
// While we don't use the notary, we need there to be one on the network
|
||||
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
|
||||
mockNet.createNotaryNode()
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
aliceNode.internals.registerInitiatedFlow(RecordCompletionFlow::class.java)
|
||||
|
@ -155,9 +155,9 @@ A CorDapp template that you can use as the basis for your own CorDapps is availa
|
||||
|
||||
https://github.com/corda/cordapp-template-kotlin.git
|
||||
|
||||
And a simple example CorDapp for you to explore basic concepts is available here:
|
||||
And a list of simple sample CorDapps for you to explore basic concepts is available here:
|
||||
|
||||
https://github.com/corda/cordapp-example.git
|
||||
https://www.corda.net/samples/
|
||||
|
||||
You can clone these repos to your local machine by running the command ``git clone [repo URL]``.
|
||||
|
||||
@ -168,9 +168,9 @@ The best way to check that everything is working fine is by taking a deeper look
|
||||
|
||||
Next, you should read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.
|
||||
|
||||
By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the
|
||||
:doc:`Hello, World tutorial <hello-world-index>`. You may want to refer to the :doc:`API documentation <corda-api>` along the
|
||||
way.
|
||||
You'll then be ready to start writing your own CorDapps. Learn how to do this in the
|
||||
:doc:`Hello, World tutorial <hello-world-index>`. You'll want to refer to the :doc:`API docs <api-index>`, the
|
||||
:doc:`flow cookbook <flow-cookbook>` and the `samples <https://www.corda.net/samples/>`_ along the way.
|
||||
|
||||
If you encounter any issues, please see the :doc:`troubleshooting` page, or get in touch with us on the
|
||||
`forums <https://discourse.corda.net/>`_ or via `slack <http://slack.corda.net/>`_.
|
||||
|
@ -10,11 +10,15 @@ quick introduction to distributed ledgers and how Corda is different, then watch
|
||||
<iframe src="https://player.vimeo.com/video/205410473" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
|
||||
</embed>
|
||||
|
||||
Want to see Corda running? Download our demonstration application `DemoBench <https://www.corda.net/downloads/>`_ or follow our :doc:`quickstart guide </quickstart-index>`.
|
||||
Want to see Corda running? Download our demonstration application `DemoBench <https://www.corda.net/downloads/>`_ or
|
||||
follow our :doc:`quickstart guide </quickstart-index>`.
|
||||
|
||||
If you want to start coding on Corda, then familiarise yourself with the :doc:`key concepts </key-concepts>`, then read our :doc:`Hello, World! tutorial </hello-world-index>`. For the background behind Corda, read the non-technical `introductory white paper`_ or for more detail, the `technical white paper`_.
|
||||
If you want to start coding on Corda, then familiarise yourself with the :doc:`key concepts </key-concepts>`, then read
|
||||
our :doc:`Hello, World! tutorial </hello-world-index>`. For the background behind Corda, read the non-technical
|
||||
`introductory white paper`_ or for more detail, the `technical white paper`_.
|
||||
|
||||
If you have questions or comments, then get in touch with us either on `Slack <https://slack.corda.net/>`_, `Discourse <https://discourse.corda.net/>`_, or write a question on `stackoverflow <https://stackoverflow.com/questions/tagged/corda>`_ .
|
||||
If you have questions or comments, then get in touch on `Slack <https://slack.corda.net/>`_ or write a question on
|
||||
`Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ .
|
||||
|
||||
We look forward to seeing what you can do with Corda!
|
||||
|
||||
|
@ -6,6 +6,6 @@ Quickstart
|
||||
|
||||
getting-set-up
|
||||
tutorial-cordapp
|
||||
running-the-demos
|
||||
Sample CorDapps <https://www.corda.net/samples/>
|
||||
building-against-master
|
||||
CLI-vs-IDE
|
@ -6,6 +6,9 @@ Here are release notes for each snapshot release from M9 onwards.
|
||||
Unreleased
|
||||
----------
|
||||
|
||||
Support for observer/regulator nodes has returned. Read :doc:`tutorial-observer-nodes` to learn more or examine the
|
||||
interest rate swaps demo.
|
||||
|
||||
Release 1.0
|
||||
-----------
|
||||
Corda 1.0 is finally here!
|
||||
|
@ -1,437 +0,0 @@
|
||||
Running the demos
|
||||
=================
|
||||
|
||||
.. contents::
|
||||
|
||||
The `Corda repository <https://github.com/corda/corda>`_ contains a number of demo programs demonstrating
|
||||
Corda's functionality:
|
||||
|
||||
1. The :ref:`trader-demo`, which shows a delivery-vs-payment atomic swap of commercial paper for cash
|
||||
2. The :ref:`irs-demo`, which shows two nodes establishing an interest rate swap and performing fixings with a
|
||||
rates oracle
|
||||
3. The :ref:`attachment-demo`, which demonstrates uploading attachments to nodes
|
||||
4. The :ref:`notary-demo`, which shows three different types of notaries and a single node getting multiple transactions
|
||||
notarised
|
||||
5. The :ref:`bank-of-corda-demo`, which shows a node acting as an issuer of assets (the Bank of Corda) while remote client
|
||||
applications request issuance of some cash on behalf of a node called Big Corporation
|
||||
|
||||
If any of the demos don't work, please raise an issue on `GitHub <https://github.com/corda/corda/issues>`_.
|
||||
|
||||
.. note:: If you are running the demos from the command line in Linux (but not macOS), you may have to install xterm.
|
||||
|
||||
.. note:: If you would like to see flow activity on the nodes type in the node terminal ``flow watch``.
|
||||
|
||||
.. _trader-demo:
|
||||
|
||||
Trader demo
|
||||
-----------
|
||||
|
||||
This demo brings up four nodes: Bank A, Bank B, Bank Of Corda, and a notary/network map node that they all use. Bank A will
|
||||
be the buyer, and requests some cash from the Bank of Corda in order to acquire commercial paper from Bank B, the seller.
|
||||
|
||||
To run from the command line in Unix:
|
||||
|
||||
1. Run ``./gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples/trader-demo/build/nodes``
|
||||
2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes
|
||||
3. Run ``./gradlew samples:trader-demo:runBank`` to instruct the bank node to issue cash and commercial paper to the buyer and seller nodes respectively.
|
||||
4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``
|
||||
|
||||
you can see flows running on both sides of transaction. Additionally you should see final trade information displayed
|
||||
to your terminal.
|
||||
|
||||
To run from the command line in Windows:
|
||||
|
||||
1. Run ``gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples\trader-demo\build\nodes``
|
||||
2. Run ``samples\trader-demo\build\nodes\runnodes`` to open up four new terminals with the four nodes
|
||||
3. Run ``gradlew samples:trader-demo:runBank`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node
|
||||
4. Run ``gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``
|
||||
|
||||
you can see flows running on both sides of transaction. Additionally you should see final trade information displayed
|
||||
to your terminal.
|
||||
|
||||
.. _irs-demo:
|
||||
|
||||
IRS demo
|
||||
--------
|
||||
|
||||
This demo brings up three nodes: Bank A, Bank B and a node that simultaneously runs a notary, a network map and an interest rates
|
||||
oracle. The two banks agree on an interest rate swap, and then do regular fixings of the deal as the time
|
||||
on a simulated clock passes.
|
||||
|
||||
To run from the command line in Unix:
|
||||
|
||||
1. Run ``./gradlew samples:irs-demo:deployNodes`` to install configs and a command line tool under ``samples/irs-demo/build``
|
||||
2. Run ``./gradlew samples:irs-demo:installDist``
|
||||
3. Move to the ``samples/irs-demo/build`` directory
|
||||
4. Run ``./nodes/runnodes`` to open up three new terminals with the three nodes (you may have to install xterm).
|
||||
|
||||
To run from the command line in Windows:
|
||||
|
||||
1. Run ``gradlew.bat samples:irs-demo:deployNodes`` to install configs and a command line tool under ``samples\irs-demo\build``
|
||||
2. Run ``gradlew.bat samples:irs-demo:installDist``
|
||||
3. Run ``cd samples\irs-demo\build`` to change current working directory
|
||||
4. Run ``nodes\runnodes`` to open up several 6 terminals, 2 for each node. First terminal is a web-server associated with every node and second one is Corda interactive shell for the node.
|
||||
|
||||
This demo also has a web app. To use this, run nodes and then navigate to
|
||||
http://localhost:10007/web/irsdemo and http://localhost:10010/web/irsdemo to see each node's view of the ledger.
|
||||
|
||||
To use the web app, click the "Create Deal" button, fill in the form, then click the "Submit" button. You can then
|
||||
use the time controls at the top left of the home page to run the fixings. Click any individual trade in the blotter to view it.
|
||||
|
||||
.. note:: The IRS web UI currently has a bug when changing the clock time where it may show no numbers or apply fixings inconsistently.
|
||||
The issues will be addressed in a future milestone release. Meanwhile, you can take a look at a simpler oracle example https://github.com/corda/oracle-example
|
||||
|
||||
.. _attachment-demo:
|
||||
|
||||
Attachment demo
|
||||
---------------
|
||||
|
||||
This demo brings up three nodes, and sends a transaction containing an attachment from one to the other.
|
||||
|
||||
To run from the command line in Unix:
|
||||
|
||||
1. Run ``./gradlew samples:attachment-demo:deployNodes`` to create a set of configs and installs under ``samples/attachment-demo/build/nodes``
|
||||
2. Run ``./samples/attachment-demo/build/nodes/runnodes`` to open up three new terminal tabs/windows with the three nodes and webserver for BankB
|
||||
3. Run ``./gradlew samples:attachment-demo:runRecipient``, which will block waiting for a trade to start
|
||||
4. Run ``./gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at the other windows to
|
||||
see the output of the demo
|
||||
|
||||
To run from the command line in Windows:
|
||||
|
||||
1. Run ``gradlew samples:attachment-demo:deployNodes`` to create a set of configs and installs under ``samples\attachment-demo\build\nodes``
|
||||
2. Run ``samples\attachment-demo\build\nodes\runnodes`` to open up three new terminal tabs/windows with the three nodes and webserver for BankB
|
||||
3. Run ``gradlew samples:attachment-demo:runRecipient``, which will block waiting for a trade to start
|
||||
4. Run ``gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at the other windows to
|
||||
see the output of the demo
|
||||
|
||||
.. _notary-demo:
|
||||
|
||||
Notary demo
|
||||
-----------
|
||||
|
||||
This demo shows a party getting transactions notarised by either a single-node or a distributed notary service.
|
||||
All versions of the demo start two counterparty nodes.
|
||||
One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation.
|
||||
|
||||
* The `Raft <https://raft.github.io/>`_ version of the demo will start three distributed notary nodes.
|
||||
* The `BFT SMaRt <https://bft-smart.github.io/library/>`_ version of the demo will start four distributed notary nodes.
|
||||
* The Single version of the demo will start a single-node validating notary service.
|
||||
* The Custom version of the demo will load and start a custom single-node notary service that is defined the demo CorDapp.
|
||||
|
||||
The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary,
|
||||
every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement.
|
||||
In the BFT SMaRt distributed notary, three signatures are required.
|
||||
You will notice that successive transactions get signed by different members of the cluster (usually allocated in a random order).
|
||||
|
||||
To run the Raft version of the demo from the command line in Unix:
|
||||
|
||||
1. Run ``./gradlew samples:notary-demo:deployNodes``, which will create node directories for all versions of the demo,
|
||||
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for
|
||||
BFT, Single and Custom notaries respectively).
|
||||
2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
||||
3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys
|
||||
|
||||
To run from the command line in Windows:
|
||||
|
||||
1. Run ``gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories
|
||||
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for
|
||||
BFT, Single and Custom notaries respectively).
|
||||
2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
||||
3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys
|
||||
|
||||
To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes
|
||||
trying to communicate each other sometime with connection errors, that's normal). For a single notary node, use ``nodesSingle``.
|
||||
For the custom notary service use ``nodesCustom`.
|
||||
|
||||
Distributed notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node.
|
||||
You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores
|
||||
by using the H2 web console:
|
||||
|
||||
- Firstly, download `H2 web console <http://www.h2database.com/html/download.html>`_ (download the "platform-independent zip"),
|
||||
and start it using a script in the extracted folder: ``sh h2/bin/h2.sh`` (or ``h2\bin\h2`` for Windows)
|
||||
|
||||
- If you are uncertain as to which version of h2 to install or if you have connectivity issues, refer to ``build.gradle``
|
||||
located in the corda directory and locate ``h2_version``. Use a client of the same major version - even if still in beta.
|
||||
|
||||
- The H2 web console should start up in a web browser tab. To connect we first need to obtain a JDBC connection string.
|
||||
Each node outputs its connection string in the terminal window as it starts up. In a terminal window where a **notary** node is running,
|
||||
look for the following string:
|
||||
|
||||
``Database connection url is : jdbc:h2:tcp://10.18.0.150:56736/node``
|
||||
|
||||
You can use the string on the right to connect to the h2 database: just paste it into the `JDBC URL` field and click *Connect*.
|
||||
You will be presented with a web application that enumerates all the available tables and provides an interface for you to query them using SQL
|
||||
|
||||
- The committed states are stored in the ``NOTARY_COMMITTED_STATES`` table (for Raft) or ``NODE_BFT_SMART_NOTARY_COMMITTED_STATES`` (for BFT).
|
||||
Note that in the Raft case the raw data is not human-readable, but we're only interested in the row count for this demo
|
||||
|
||||
.. _bank-of-corda-demo:
|
||||
|
||||
Bank Of Corda demo
|
||||
------------------
|
||||
|
||||
This demo brings up three nodes: a notary, a node acting as the Bank of Corda that accepts requests for issuance of some asset
|
||||
and a node acting as Big Corporation which requests issuance of an asset (cash in this example).
|
||||
|
||||
Upon receipt of a request the Bank of Corda node self-issues the asset and then transfers ownership to the requester
|
||||
after successful notarisation and recording of the issue transaction on the ledger.
|
||||
|
||||
.. note:: The Bank of Corda is somewhat like a "Bitcoin faucet" that dispenses free bitcoins to developers for
|
||||
testing and experimentation purposes.
|
||||
|
||||
To run from the command line in Unix:
|
||||
|
||||
1. Run ``./gradlew samples:bank-of-corda-demo:deployNodes`` to create a set of configs and installs under ``samples/bank-of-corda-demo/build/nodes``
|
||||
2. Run ``./samples/bank-of-corda-demo/build/nodes/runnodes`` to open up three new terminal tabs/windows with the three nodes
|
||||
3. Run ``./gradlew samples:bank-of-corda-demo:runRPCCashIssue`` to trigger a cash issuance request
|
||||
4. Run ``./gradlew samples:bank-of-corda-demo:runWebCashIssue`` to trigger another cash issuance request.
|
||||
Now look at your terminal tab/window to see the output of the demo
|
||||
|
||||
To run from the command line in Windows:
|
||||
|
||||
1. Run ``gradlew samples:bank-of-corda-demo:deployNodes`` to create a set of configs and installs under ``samples\bank-of-corda-demo\build\nodes``
|
||||
2. Run ``samples\bank-of-corda-demo\build\nodes\runnodes`` to open up three new terminal tabs/windows with the three nodes
|
||||
3. Run ``gradlew samples:bank-of-corda-demo:runRPCCashIssue`` to trigger a cash issuance request
|
||||
4. Run ``gradlew samples:bank-of-corda-demo:runWebCashIssue`` to trigger another cash issuance request.
|
||||
Now look at the your terminal tab/window to see the output of the demo
|
||||
|
||||
.. note:: To verify that the Bank of Corda node is alive and running, navigate to the following URL:
|
||||
http://localhost:10007/api/bank/date
|
||||
|
||||
In the window you run the command you should see (in case of Web, RPC is simmilar):
|
||||
|
||||
- Requesting Cash via Web ...
|
||||
- Successfully processed Cash Issue request
|
||||
|
||||
If you want to see flow activity enter in node's shell ``flow watch``. It will display all state machines
|
||||
running currently on the node.
|
||||
|
||||
Launch the Explorer application to visualize the issuance and transfer of cash for each node:
|
||||
|
||||
``./gradlew tools:explorer:run`` (on Unix) or ``gradlew tools:explorer:run`` (on Windows)
|
||||
|
||||
Using the following login details:
|
||||
|
||||
- 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.
|
||||
|
||||
.. _simm-demo:
|
||||
|
||||
SIMM and Portfolio Demo - aka the Initial Margin Agreement Demo
|
||||
---------------------------------------------------------------
|
||||
|
||||
Background and SIMM Introduction
|
||||
********************************
|
||||
|
||||
This app is a demonstration of how Corda can be used for the real world requirement of initial margin calculation and
|
||||
agreement; featuring the integration of complex and industry proven third party libraries into Corda nodes.
|
||||
|
||||
SIMM is an acronym for "Standard Initial Margin Model". It is effectively the calculation of a "margin" that is paid
|
||||
by one party to another when they agree a trade on certain types of transaction.
|
||||
|
||||
The SIMM was introduced to standardise the calculation of how much margin counterparties charge each other on their
|
||||
bilateral transactions. Before SIMM, each counterparty computed margins according to its own model and it was made it very
|
||||
difficult to agree the exact margin with the counterparty that faces the same trade on the other side.
|
||||
|
||||
To enact this, in September 2016, the ISDA committee - with full backing from various governing bodies -
|
||||
`issued a ruling on what is known as the ISDA SIMM ™ model <http://www2.isda.org/news/isda-simm-deployed-today-new-industry-standard-for-calculating-initial-margin-widely-adopted-by-market-participants>`_,
|
||||
a way of fairly and consistently calculating this margin. Any parties wishing to trade a financial product that is
|
||||
covered under this ruling would, independently, use this model and calculate their margin payment requirement,
|
||||
agree it with their trading counterparty and then pay (or receive, depending on the results of this calculation)
|
||||
this amount. In the case of disagreement that is not resolved in a timely fashion, this payment would increase
|
||||
and so therefore it is in the parties' interest to reach agreement in as short as time frame as possible.
|
||||
|
||||
To be more accurate, the SIMM calculation is not performed on just one trade - it is calculated on an aggregate of
|
||||
intermediary values (which in this model are sensitivities to risk factors) from a portfolio of trades; therefore
|
||||
the input to a SIMM is actually this data, not the individual trades themselves.
|
||||
|
||||
Also note that implementations of the SIMM are actually protected and subject to license restrictions by ISDA
|
||||
(this is due to the model itself being protected). We were fortunate enough to technically partner with
|
||||
`OpenGamma <http://www.opengamma.com>`_ who allowed us to demonstrate the SIMM process using their proprietary model.
|
||||
In the source code released, we have replaced their analytics engine with very simple stub functions that allow
|
||||
the process to run without actually calculating correct values, and can easily be swapped out in place for their real libraries.
|
||||
|
||||
What happens in the demo (notionally)
|
||||
*************************************
|
||||
|
||||
Preliminaries
|
||||
- Ensure that there are a number of live trades with another party based on financial products that are covered under the
|
||||
ISDA SIMM agreement (if none, then use the demo to enter some simple trades as described below).
|
||||
|
||||
Initial Margin Agreement Process
|
||||
- Agree that one will be performing the margining calculation against a portfolio of trades with another party, and agree the trades in that portfolio. In practice, one node will start the flow but it does not matter which node does.
|
||||
- Individually (at the node level), identify the data (static, reference etc) one will need in order to be able to calculate the metrics on those trades
|
||||
- Confirm with the other counterparty the dataset from the above set
|
||||
- Calculate any intermediary steps and values needed for the margin calculation (ie sensitivities to risk factors)
|
||||
- Agree on the results of these steps
|
||||
- Calculate the initial margin
|
||||
- Agree on the calculation of the above with the other party
|
||||
- In practice, pay (or receive) this margin (omitted for the sake of complexity for this example)
|
||||
|
||||
Demo execution (step by step)
|
||||
*****************************
|
||||
|
||||
**Setting up the Corda infrastructure**
|
||||
|
||||
To run from the command line in Unix:
|
||||
|
||||
1. Deploy the nodes using ``./gradlew samples:simm-valuation-demo:deployNodes``
|
||||
2. Run the nodes using ``./samples/simm-valuation-demo/build/nodes/runnodes``
|
||||
|
||||
To run from the command line in Windows:
|
||||
|
||||
1. Deploy the nodes using ``gradlew samples:simm-valuation-demo:deployNodes``
|
||||
2. Run the nodes using ``samples\simm-valuation-demo\build\nodes\runnodes``
|
||||
|
||||
**Getting Bank A's details**
|
||||
|
||||
From the command line run
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl http://localhost:10005/api/simmvaluationdemo/whoami
|
||||
|
||||
The response should be something like
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
{
|
||||
"self" : {
|
||||
"id" : "8Kqd4oWdx4KQGHGQW3FwXHQpjiv7cHaSsaAWMwRrK25bBJj792Z4rag7EtA",
|
||||
"text" : "C=GB,L=London,O=Bank A"
|
||||
},
|
||||
"counterparties" : [
|
||||
{
|
||||
"id" : "8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM",
|
||||
"text" : "C=JP,L=Tokyo,O=Bank C"
|
||||
},
|
||||
{
|
||||
"id" : "8Kqd4oWdx4KQGHGTBm34eCM2nrpcWKeM1ZG3DUYat3JTFUQTwB3Lv2WbPM8",
|
||||
"text" : "C=US,L=New York,O=Bank B"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Now, if we ask the same question of Bank C we will see that it's id matches the id for Bank C as a counter
|
||||
party to Bank A and Bank A will appear as a counter party
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" -X GET http://localhost:10011/api/simmvaluationdemo/whoami
|
||||
|
||||
**Creating a trade with Bank C**
|
||||
|
||||
In what follows, we assume we are Bank A (which is listening on port 10005)
|
||||
|
||||
Notice the id field in the output of the ``whoami`` command. We are going to use the id assocatied
|
||||
with Bank C, one of our counter parties, to create a trade. The general command for this is:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" -X PUT -d <<<JSON representation of the trade>>> http://localhost:10005/api/simmvaluationdemo/<<<counter party id>>>/trades
|
||||
|
||||
where the representation of the trade is
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
{
|
||||
"id" : "trade1",
|
||||
"description" : "desc",
|
||||
"tradeDate" : [ 2016, 6, 6 ],
|
||||
"convention" : "EUR_FIXED_1Y_EURIBOR_3M",
|
||||
"startDate" : [ 2016, 6, 6 ],
|
||||
"endDate" : [ 2020, 1, 2 ],
|
||||
"buySell" : "BUY",
|
||||
"notional" : "1000",
|
||||
"fixedRate" : "0.1"
|
||||
}
|
||||
|
||||
Continuing our example, the specific command we would run is
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" \
|
||||
-X PUT \
|
||||
-d '{"id":"trade1","description" : "desc","tradeDate" : [ 2016, 6, 6 ], "convention" : "EUR_FIXED_1Y_EURIBOR_3M", "startDate" : [ 2016, 6, 6 ], "endDate" : [ 2020, 1, 2 ], "buySell" : "BUY", "notional" : "1000", "fixedRate" : "0.1"}' \
|
||||
http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades
|
||||
|
||||
With an expected response of
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
HTTP/1.1 202 Accepted
|
||||
Date: Thu, 28 Sep 2017 17:19:39 GMT
|
||||
Content-Type: text/plain
|
||||
Access-Control-Allow-Origin: *
|
||||
Content-Length: 2
|
||||
Server: Jetty(9.3.9.v20160517)
|
||||
|
||||
**Verifying trade completion**
|
||||
|
||||
With the trade completed and stored by both parties, the complete list of trades with our couterparty can be seen with the following command
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -X GET http://localhost:10005/api/simmvaluationdemo/<<<counter party id>>>/trades
|
||||
|
||||
The command for our example, using Bank A, would thus be
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades
|
||||
|
||||
whilst a specific trade can be seen with
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -X GET http://localhost:10005/api/simmvaluationdemo/<<<counter party id>>>/trades/<<<trade id>>>
|
||||
|
||||
If we look at the trade we created above, we assigned it the id "trade1", the complete command in this case would be
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades/trade1
|
||||
|
||||
**Generating a valuation**
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d <<<JSON representation>>>
|
||||
http://localhost:10005/api/simmvaluationdemo/<<<counter party id>>>/portfolio/valuations/calculate
|
||||
|
||||
Again, the specific command to continue our example would be
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d '{"valuationDate":[2016,6,6]}' \
|
||||
http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzLumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/portfolio/valuations/calculate
|
||||
|
||||
**Viewing a valuation**
|
||||
|
||||
In the same way we can ask for specific instances of trades with a counter party, we can request details of valuations
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" -X GET http://localhost:10005/api/simmvaluationdemo/<<<counter party id>>>/portfolio/valuations
|
||||
|
||||
The specific command for out Bank A example is
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" \
|
||||
-X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65YwQM/portfolio/valuations
|
||||
|
||||
|
||||
|
||||
|
||||
|
42
docs/source/tutorial-observer-nodes.rst
Normal file
42
docs/source/tutorial-observer-nodes.rst
Normal file
@ -0,0 +1,42 @@
|
||||
.. highlight:: kotlin
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||
|
||||
Observer nodes
|
||||
==============
|
||||
|
||||
Posting transactions to an observer node is a common requirement in finance, where regulators often want
|
||||
to receive comprehensive reporting on all actions taken. By running their own node, regulators can receive a stream
|
||||
of digitally signed, de-duplicated reports useful for later processing.
|
||||
|
||||
Adding support for observer nodes to your application is easy. The IRS (interest rate swap) demo shows to do it.
|
||||
|
||||
Just define a new flow that wraps the SendTransactionFlow/ReceiveTransactionFlow, as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 1
|
||||
:end-before: DOCEND 1
|
||||
|
||||
In this example, the ``AutoOfferFlow`` is the business logic, and we define two very short and simple flows to send
|
||||
the transaction to the regulator. There are two important aspects to note here:
|
||||
|
||||
1. The ``ReportToRegulatorFlow`` is marked as an ``@InitiatingFlow`` because it will start a new conversation, context
|
||||
free, with the regulator.
|
||||
2. The ``ReceiveRegulatoryReportFlow`` uses ``ReceiveTransactionFlow`` in a special way - it tells it to send the
|
||||
transaction to the vault for processing, including all states even if not involving our public keys. This is required
|
||||
because otherwise the vault will ignore states that don't list any of the node's public keys, but in this case,
|
||||
we do want to passively observe states we can't change. So overriding this behaviour is required.
|
||||
|
||||
If the states define a relational mapping (see :doc:`api-persistence`) then the regulator will be able to query the
|
||||
reports from their database and observe new transactions coming in via RPC.
|
||||
|
||||
.. warning:: Nodes which act as both observers and which directly take part in the ledger are not supported at this
|
||||
time. In particular, coin selection may return states which you do not have the private keys to be able to sign
|
||||
for. Future versions of Corda may address this issue, but for now, if you wish to both participate in the ledger
|
||||
and also observe transactions that you can't sign for you will need to run two nodes and have two separate
|
||||
identities.
|
@ -19,4 +19,5 @@ Tutorials
|
||||
tutorial-custom-notary
|
||||
tutorial-tear-offs
|
||||
tutorial-attachments
|
||||
event-scheduling
|
||||
event-scheduling
|
||||
tutorial-observer-nodes
|
@ -87,9 +87,11 @@ object TwoPartyTradeFlow {
|
||||
// Verify and sign the transaction.
|
||||
progressTracker.currentStep = VERIFYING_AND_SIGNING
|
||||
|
||||
// DOCSTART 07
|
||||
// Sync identities to ensure we know all of the identities involved in the transaction we're about to
|
||||
// be asked to sign
|
||||
subFlow(IdentitySyncFlow.Receive(otherSideSession))
|
||||
// DOCEND 07
|
||||
|
||||
// DOCSTART 5
|
||||
val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) {
|
||||
|
@ -230,8 +230,7 @@ class CommercialPaperTestsGeneric {
|
||||
|
||||
// @Test
|
||||
@Ignore
|
||||
fun `issue move and then redeem`() {
|
||||
initialiseTestSerialization()
|
||||
fun `issue move and then redeem`() = withTestSerialization {
|
||||
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY))
|
||||
val databaseAlice = aliceDatabaseAndServices.first
|
||||
aliceServices = aliceDatabaseAndServices.second
|
||||
@ -311,6 +310,5 @@ class CommercialPaperTestsGeneric {
|
||||
validRedemption.toLedgerTransaction(aliceServices).verify()
|
||||
// soft lock not released after success either!!! (as transaction not recorded)
|
||||
}
|
||||
resetTestSerialization()
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import java.security.KeyPair
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
|
||||
class CashTests : TestDependencyInjectionBase() {
|
||||
class CashTests {
|
||||
private val defaultRef = OpaqueBytes(ByteArray(1, { 1 }))
|
||||
private val defaultIssuer = MEGA_CORP.ref(defaultRef)
|
||||
private val inState = Cash.State(
|
||||
@ -51,7 +51,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
private lateinit var vaultStatesUnconsumed: List<StateAndRef<Cash.State>>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
fun setUp() = withTestSerialization {
|
||||
LogHelper.setLevel(NodeVaultService::class)
|
||||
megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP_KEY)
|
||||
val databaseAndServices = makeTestDatabaseAndMockServices(cordappPackages = listOf("net.corda.finance.contracts.asset"), keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY))
|
||||
@ -72,7 +72,6 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
database.transaction {
|
||||
vaultStatesUnconsumed = miniCorpServices.vaultService.queryBy<Cash.State>().states
|
||||
}
|
||||
resetTestSerialization()
|
||||
}
|
||||
|
||||
@After
|
||||
@ -154,8 +153,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateIssueRaw() {
|
||||
initialiseTestSerialization()
|
||||
fun generateIssueRaw() = withTestSerialization {
|
||||
// Test generation works.
|
||||
val tx: WireTransaction = TransactionBuilder(notary = null).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY)
|
||||
@ -170,8 +168,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateIssueFromAmount() {
|
||||
initialiseTestSerialization()
|
||||
fun generateIssueFromAmount() = withTestSerialization {
|
||||
// Test issuance from an issued amount
|
||||
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34)
|
||||
val tx: WireTransaction = TransactionBuilder(notary = null).apply {
|
||||
@ -239,8 +236,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
* cash inputs.
|
||||
*/
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `reject issuance with inputs`() {
|
||||
initialiseTestSerialization()
|
||||
fun `reject issuance with inputs`() = withTestSerialization {
|
||||
// Issue some cash
|
||||
var ptx = TransactionBuilder(DUMMY_NOTARY)
|
||||
|
||||
@ -251,6 +247,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
ptx = TransactionBuilder(DUMMY_NOTARY)
|
||||
ptx.addInputState(tx.tx.outRef<Cash.State>(0))
|
||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -515,8 +512,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
* Try exiting an amount which matches a single state.
|
||||
*/
|
||||
@Test
|
||||
fun generateSimpleExit() {
|
||||
initialiseTestSerialization()
|
||||
fun generateSimpleExit() = withTestSerialization {
|
||||
val wtx = makeExit(miniCorpServices, 100.DOLLARS, MEGA_CORP, 1)
|
||||
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
||||
assertEquals(0, wtx.outputs.size)
|
||||
@ -531,8 +527,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
* Try exiting an amount smaller than the smallest available input state, and confirm change is generated correctly.
|
||||
*/
|
||||
@Test
|
||||
fun generatePartialExit() {
|
||||
initialiseTestSerialization()
|
||||
fun generatePartialExit() = withTestSerialization {
|
||||
val wtx = makeExit(miniCorpServices, 50.DOLLARS, MEGA_CORP, 1)
|
||||
val actualInput = wtx.inputs.single()
|
||||
// Filter the available inputs and confirm exactly one has been used
|
||||
@ -549,53 +544,52 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
* Try exiting a currency we don't have.
|
||||
*/
|
||||
@Test
|
||||
fun generateAbsentExit() {
|
||||
initialiseTestSerialization()
|
||||
fun generateAbsentExit() = withTestSerialization {
|
||||
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 1) }
|
||||
Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Try exiting with a reference mis-match.
|
||||
*/
|
||||
@Test
|
||||
fun generateInvalidReferenceExit() {
|
||||
initialiseTestSerialization()
|
||||
fun generateInvalidReferenceExit() = withTestSerialization {
|
||||
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 2) }
|
||||
Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Try exiting an amount greater than the maximum available.
|
||||
*/
|
||||
@Test
|
||||
fun generateInsufficientExit() {
|
||||
initialiseTestSerialization()
|
||||
fun generateInsufficientExit() = withTestSerialization {
|
||||
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 1000.DOLLARS, MEGA_CORP, 1) }
|
||||
Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Try exiting for an owner with no states
|
||||
*/
|
||||
@Test
|
||||
fun generateOwnerWithNoStatesExit() {
|
||||
initialiseTestSerialization()
|
||||
fun generateOwnerWithNoStatesExit() = withTestSerialization {
|
||||
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, CHARLIE, 1) }
|
||||
Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Try exiting when vault is empty
|
||||
*/
|
||||
@Test
|
||||
fun generateExitWithEmptyVault() {
|
||||
initialiseTestSerialization()
|
||||
fun generateExitWithEmptyVault() = withTestSerialization {
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), OUR_IDENTITY_1)
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateSimpleDirectSpend() {
|
||||
initialiseTestSerialization()
|
||||
fun generateSimpleDirectSpend() = withTestSerialization {
|
||||
val wtx =
|
||||
database.transaction {
|
||||
makeSpend(100.DOLLARS, THEIR_IDENTITY_1)
|
||||
@ -609,8 +603,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateSimpleSpendWithParties() {
|
||||
initialiseTestSerialization()
|
||||
fun generateSimpleSpendWithParties() = withTestSerialization {
|
||||
database.transaction {
|
||||
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
@ -621,8 +614,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateSimpleSpendWithChange() {
|
||||
initialiseTestSerialization()
|
||||
fun generateSimpleSpendWithChange() = withTestSerialization {
|
||||
val wtx =
|
||||
database.transaction {
|
||||
makeSpend(10.DOLLARS, THEIR_IDENTITY_1)
|
||||
@ -647,8 +639,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateSpendWithTwoInputs() {
|
||||
initialiseTestSerialization()
|
||||
fun generateSpendWithTwoInputs() = withTestSerialization {
|
||||
val wtx =
|
||||
database.transaction {
|
||||
makeSpend(500.DOLLARS, THEIR_IDENTITY_1)
|
||||
@ -664,8 +655,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateSpendMixedDeposits() {
|
||||
initialiseTestSerialization()
|
||||
fun generateSpendMixedDeposits() = withTestSerialization {
|
||||
val wtx =
|
||||
database.transaction {
|
||||
val wtx = makeSpend(580.DOLLARS, THEIR_IDENTITY_1)
|
||||
@ -686,8 +676,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateSpendInsufficientBalance() {
|
||||
initialiseTestSerialization()
|
||||
fun generateSpendInsufficientBalance() = withTestSerialization {
|
||||
database.transaction {
|
||||
|
||||
val e: InsufficientBalanceException = assertFailsWith("balance") {
|
||||
@ -699,6 +688,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
makeSpend(81.SWISS_FRANCS, THEIR_IDENTITY_1)
|
||||
}
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
/**
|
||||
@ -825,8 +815,7 @@ class CashTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiSpend() {
|
||||
initialiseTestSerialization()
|
||||
fun multiSpend() = withTestSerialization {
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
database.transaction {
|
||||
val payments = listOf(
|
||||
|
@ -19,7 +19,6 @@ import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
@ -64,11 +63,6 @@ class ObligationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun reset() {
|
||||
resetTestSerialization()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trivial() {
|
||||
transaction {
|
||||
@ -139,25 +133,23 @@ class ObligationTests {
|
||||
command(MINI_CORP_PUBKEY) { Obligation.Commands.Issue() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
initialiseTestSerialization()
|
||||
// Test generation works.
|
||||
val tx = TransactionBuilder(notary = null).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = CHARLIE, notary = DUMMY_NOTARY)
|
||||
}.toWireTransaction(miniCorpServices)
|
||||
assertTrue(tx.inputs.isEmpty())
|
||||
val expected = Obligation.State(
|
||||
obligor = MINI_CORP,
|
||||
quantity = 100.DOLLARS.quantity,
|
||||
beneficiary = CHARLIE,
|
||||
template = megaCorpDollarSettlement
|
||||
)
|
||||
assertEquals(tx.getOutput(0), expected)
|
||||
assertTrue(tx.commands[0].value is Obligation.Commands.Issue)
|
||||
assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0])
|
||||
resetTestSerialization()
|
||||
|
||||
withTestSerialization {
|
||||
// Test generation works.
|
||||
val tx = TransactionBuilder(notary = null).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = CHARLIE, notary = DUMMY_NOTARY)
|
||||
}.toWireTransaction(miniCorpServices)
|
||||
assertTrue(tx.inputs.isEmpty())
|
||||
val expected = Obligation.State(
|
||||
obligor = MINI_CORP,
|
||||
quantity = 100.DOLLARS.quantity,
|
||||
beneficiary = CHARLIE,
|
||||
template = megaCorpDollarSettlement
|
||||
)
|
||||
assertEquals(tx.getOutput(0), expected)
|
||||
assertTrue(tx.commands[0].value is Obligation.Commands.Issue)
|
||||
assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0])
|
||||
}
|
||||
// We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer.
|
||||
transaction {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -214,8 +206,7 @@ class ObligationTests {
|
||||
* cash inputs.
|
||||
*/
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `reject issuance with inputs`() {
|
||||
initialiseTestSerialization()
|
||||
fun `reject issuance with inputs`() = withTestSerialization {
|
||||
// Issue some obligation
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
@ -228,12 +219,12 @@ class ObligationTests {
|
||||
ptx.addInputState(tx.outRef<Obligation.State<Currency>>(0))
|
||||
Obligation<Currency>().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
Unit
|
||||
}
|
||||
|
||||
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
||||
@Test
|
||||
fun `generate close-out net transaction`() {
|
||||
initialiseTestSerialization()
|
||||
fun `generate close-out net transaction`() = withTestSerialization {
|
||||
val obligationAliceToBob = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
|
||||
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
|
||||
@ -244,8 +235,7 @@ class ObligationTests {
|
||||
|
||||
/** Test generating a transaction to net two obligations of the different sizes, and confirm the balance is correct. */
|
||||
@Test
|
||||
fun `generate close-out net transaction with remainder`() {
|
||||
initialiseTestSerialization()
|
||||
fun `generate close-out net transaction with remainder`() = withTestSerialization {
|
||||
val obligationAliceToBob = getStateAndRef((2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
|
||||
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
|
||||
@ -259,8 +249,7 @@ class ObligationTests {
|
||||
|
||||
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
||||
@Test
|
||||
fun `generate payment net transaction`() {
|
||||
initialiseTestSerialization()
|
||||
fun `generate payment net transaction`() = withTestSerialization {
|
||||
val obligationAliceToBob = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
|
||||
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
|
||||
@ -271,8 +260,7 @@ class ObligationTests {
|
||||
|
||||
/** Test generating a transaction to two obligations, where one is bigger than the other and therefore there is a remainder. */
|
||||
@Test
|
||||
fun `generate payment net transaction with remainder`() {
|
||||
initialiseTestSerialization()
|
||||
fun `generate payment net transaction with remainder`() = withTestSerialization {
|
||||
val obligationAliceToBob = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
|
||||
val obligationAliceToBobState = obligationAliceToBob.state.data
|
||||
val obligationBobToAlice = getStateAndRef((2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
|
||||
@ -294,8 +282,7 @@ class ObligationTests {
|
||||
|
||||
/** Test generating a transaction to mark outputs as having defaulted. */
|
||||
@Test
|
||||
fun `generate set lifecycle`() {
|
||||
initialiseTestSerialization()
|
||||
fun `generate set lifecycle`() = withTestSerialization {
|
||||
// We don't actually verify the states, this is just here to make things look sensible
|
||||
val dueBefore = TEST_TX_TIME - 7.days
|
||||
|
||||
@ -333,8 +320,7 @@ class ObligationTests {
|
||||
|
||||
/** Test generating a transaction to settle an obligation. */
|
||||
@Test
|
||||
fun `generate settlement transaction`() {
|
||||
initialiseTestSerialization()
|
||||
fun `generate settlement transaction`() = withTestSerialization {
|
||||
val cashTx = TransactionBuilder(null).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP, DUMMY_NOTARY)
|
||||
}.toWireTransaction(miniCorpServices)
|
||||
@ -926,8 +912,7 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `summing balances due between parties`() {
|
||||
initialiseTestSerialization()
|
||||
fun `summing balances due between parties`() = withTestSerialization {
|
||||
val simple: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = mapOf(Pair(Pair(ALICE, BOB), Amount(100000000, GBP)))
|
||||
val expected: Map<AbstractParty, Long> = mapOf(Pair(ALICE, -100000000L), Pair(BOB, 100000000L))
|
||||
val actual = sumAmountsDue(simple)
|
||||
|
@ -32,11 +32,11 @@ public class ApiScanner implements Plugin<Project> {
|
||||
project.getLogger().info("Adding scanApi task to {}", project.getName());
|
||||
project.getTasks().create("scanApi", ScanApi.class, scanTask -> {
|
||||
scanTask.setClasspath(compilationClasspath(project.getConfigurations()));
|
||||
// Automatically creates a dependency on jar tasks.
|
||||
scanTask.setSources(project.files(jarTasks));
|
||||
scanTask.setExcludeClasses(extension.getExcludeClasses());
|
||||
scanTask.setVerbose(extension.isVerbose());
|
||||
scanTask.setEnabled(extension.isEnabled());
|
||||
scanTask.dependsOn(jarTasks);
|
||||
|
||||
// Declare this ScanApi task to be a dependency of any
|
||||
// GenerateApi tasks belonging to any of our ancestors.
|
||||
|
@ -18,10 +18,12 @@ class Cordformation : Plugin<Project> {
|
||||
* @return A file handle to the file in the JAR.
|
||||
*/
|
||||
fun getPluginFile(project: Project, filePathInJar: String): File {
|
||||
val archive: File? = project.rootProject.buildscript.configurations.single { it.name == "classpath" }.find {
|
||||
it.name.contains("cordformation")
|
||||
}
|
||||
return project.rootProject.resources.text.fromArchiveEntry(archive, filePathInJar).asFile()
|
||||
val archive: File? = project.rootProject.buildscript.configurations
|
||||
.single { it.name == "classpath" }
|
||||
.find { it.name.contains("cordformation") }
|
||||
return project.rootProject.resources.text
|
||||
.fromArchiveEntry(archive, filePathInJar)
|
||||
.asFile()
|
||||
}
|
||||
|
||||
val executableFileMode = "0755".toInt(8)
|
||||
|
@ -104,7 +104,7 @@ private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: I
|
||||
delay 0.5
|
||||
tell app "System Events" to tell process "Terminal" to keystroke "t" using command down
|
||||
delay 0.5
|
||||
do script "bash -c 'cd $dir; ${command.joinToString(" ")} && exit'" in selected tab of the front window
|
||||
do script "bash -c 'cd \"$dir\" ; \"${command.joinToString("""\" \"""")}\" && exit'" in selected tab of the front window
|
||||
end tell""")
|
||||
}
|
||||
OS.WINDOWS -> {
|
||||
|
@ -11,6 +11,10 @@ dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
// TODO: remove the forced update of commons-collections and beanutils when artemis updates them
|
||||
compile "org.apache.commons:commons-collections4:${commons_collections_version}"
|
||||
compile "commons-beanutils:commons-beanutils:${beanutils_version}"
|
||||
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
|
||||
compile "org.apache.activemq:artemis-commons:${artemis_version}"
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.corda.nodeapi
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.MessageRecipientGroup
|
||||
import net.corda.core.messaging.MessageRecipients
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
@ -42,11 +40,6 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
|
||||
val hostAndPort: NetworkHostAndPort
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
data class NetworkMapAddress(override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
|
||||
override val queueName: String get() = NETWORK_MAP_QUEUE
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the class used to implement [SingleMessageRecipient], for now. Note that in future this class
|
||||
* may change or evolve and code that relies upon it being a simple host/port may not function correctly.
|
||||
@ -60,11 +53,8 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
|
||||
companion object {
|
||||
fun asSingleNode(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort): NodeAddress {
|
||||
return NodeAddress("$PEERS_PREFIX${peerIdentity.toBase58String()}", hostAndPort)
|
||||
}
|
||||
}
|
||||
constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) :
|
||||
this("$PEERS_PREFIX${peerIdentity.toBase58String()}", hostAndPort)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,13 +72,4 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
|
||||
|
||||
/** The config object is used to pass in the passwords for the certificate KeyStore and TrustStore */
|
||||
abstract val config: SSLConfiguration?
|
||||
|
||||
// Used for bridges creation.
|
||||
fun getArtemisPeerAddress(party: Party, address: NetworkHostAndPort, netMapName: CordaX500Name? = null): ArtemisPeerAddress {
|
||||
return if (party.name == netMapName) {
|
||||
NetworkMapAddress(address)
|
||||
} else {
|
||||
NodeAddress.asSingleNode(party.owningKey, address) // It also takes care of services nodes treated as peer nodes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import org.apache.qpid.proton.amqp.UnsignedLong
|
||||
|
||||
/**
|
||||
* R3 AMQP assigned enterprise number
|
||||
*
|
||||
* see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers)
|
||||
*
|
||||
* Repeated here for brevity:
|
||||
* 50530 - R3 - Mike Hearn - mike&r3.com
|
||||
*/
|
||||
const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl(32 + 16)
|
||||
|
||||
/**
|
||||
* AMQP desriptor ID's for our custom types.
|
||||
*
|
||||
* NEVER DELETE OR CHANGE THE ID ASSOCIATED WITH A TYPE
|
||||
*
|
||||
* these are encoded as part of a serialised blob and doing so would render us unable to
|
||||
* de-serialise that blob!!!
|
||||
*/
|
||||
enum class AMQPDescriptorRegistry(val id: Long) {
|
||||
ENVELOPE(1),
|
||||
SCHEMA(2),
|
||||
OBJECT_DESCRIPTOR(3),
|
||||
FIELD(4),
|
||||
COMPOSITE_TYPE(5),
|
||||
RESTRICTED_TYPE(6),
|
||||
CHOICE(7),
|
||||
REFERENCED_OBJECT(8),
|
||||
TRANSFORM_SCHEMA(9),
|
||||
TRANSFORM_ELEMENT(10),
|
||||
TRANSFORM_ELEMENT_KEY(11)
|
||||
;
|
||||
|
||||
val amqpDescriptor = UnsignedLong(id or DESCRIPTOR_TOP_32BITS)
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import org.apache.qpid.proton.codec.DescribedTypeConstructor
|
||||
|
||||
import java.io.NotSerializableException
|
||||
|
||||
/**
|
||||
* This class wraps all serialized data, so that the schema can be carried along with it. We will provide various
|
||||
* internal utilities to decompose and recompose with/without schema etc so that e.g. we can store objects with a
|
||||
* (relationally) normalised out schema to avoid excessive duplication.
|
||||
*/
|
||||
// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we
|
||||
// TODO: don't recognise a type descriptor.
|
||||
data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: TransformsSchema) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Envelope> {
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.ENVELOPE.amqpDescriptor
|
||||
val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR)
|
||||
|
||||
// described list should either be two or three elements long
|
||||
private const val ENVELOPE_WITHOUT_TRANSFORMS = 2
|
||||
private const val ENVELOPE_WITH_TRANSFORMS = 3
|
||||
|
||||
private const val BLOB_IDX = 0
|
||||
private const val SCHEMA_IDX = 1
|
||||
private const val TRANSFORMS_SCHEMA_IDX = 2
|
||||
|
||||
fun get(data: Data): Envelope {
|
||||
val describedType = data.`object` as DescribedType
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
|
||||
}
|
||||
val list = describedType.described as List<*>
|
||||
|
||||
// We need to cope with objects serialised without the transforms header element in the
|
||||
// envelope
|
||||
val transformSchema: Any? = when (list.size) {
|
||||
ENVELOPE_WITHOUT_TRANSFORMS -> null
|
||||
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX]
|
||||
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
|
||||
}
|
||||
|
||||
return newInstance(listOf(list[BLOB_IDX], Schema.get(list[SCHEMA_IDX]!!),
|
||||
TransformsSchema.newInstance(transformSchema)))
|
||||
}
|
||||
|
||||
// This separation of functions is needed as this will be the entry point for the default
|
||||
// AMQP decoder if one is used (see the unit tests).
|
||||
override fun newInstance(described: Any?): Envelope {
|
||||
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
|
||||
|
||||
// We need to cope with objects serialised without the transforms header element in the
|
||||
// envelope
|
||||
val transformSchema = when (list.size) {
|
||||
ENVELOPE_WITHOUT_TRANSFORMS -> TransformsSchema.newInstance(null)
|
||||
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX] as TransformsSchema
|
||||
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
|
||||
}
|
||||
|
||||
return Envelope(list[BLOB_IDX], list[SCHEMA_IDX] as Schema, transformSchema)
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = Envelope::class.java
|
||||
}
|
||||
|
||||
override fun getDescriptor(): Any = DESCRIPTOR
|
||||
|
||||
override fun getDescribed(): Any = listOf(obj, schema, transformsSchema)
|
||||
}
|
@ -10,7 +10,6 @@ import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.amqp.UnsignedInteger
|
||||
import org.apache.qpid.proton.amqp.UnsignedLong
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import org.apache.qpid.proton.codec.DescribedTypeConstructor
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.*
|
||||
@ -18,76 +17,18 @@ import java.util.*
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema
|
||||
|
||||
/**
|
||||
* R3 AMQP assigned enterprise number
|
||||
*
|
||||
* see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers)
|
||||
*
|
||||
* Repeated here for brevity:
|
||||
* 50530 - R3 - Mike Hearn - mike&r3.com
|
||||
*/
|
||||
const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000
|
||||
|
||||
const val DESCRIPTOR_DOMAIN: String = "net.corda"
|
||||
|
||||
// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB
|
||||
val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray())
|
||||
|
||||
private enum class DescriptorRegistry(val id: Long) {
|
||||
|
||||
ENVELOPE(1),
|
||||
SCHEMA(2),
|
||||
OBJECT_DESCRIPTOR(3),
|
||||
FIELD(4),
|
||||
COMPOSITE_TYPE(5),
|
||||
RESTRICTED_TYPE(6),
|
||||
CHOICE(7),
|
||||
REFERENCED_OBJECT(8),
|
||||
;
|
||||
|
||||
val amqpDescriptor = UnsignedLong(id or DESCRIPTOR_TOP_32BITS)
|
||||
}
|
||||
|
||||
/**
|
||||
* This class wraps all serialized data, so that the schema can be carried along with it. We will provide various internal utilities
|
||||
* to decompose and recompose with/without schema etc so that e.g. we can store objects with a (relationally) normalised out schema to
|
||||
* avoid excessive duplication.
|
||||
*/
|
||||
// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we don't recognise a type descriptor.
|
||||
data class Envelope(val obj: Any?, val schema: Schema) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Envelope> {
|
||||
val DESCRIPTOR = DescriptorRegistry.ENVELOPE.amqpDescriptor
|
||||
val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR)
|
||||
|
||||
fun get(data: Data): Envelope {
|
||||
val describedType = data.`object` as DescribedType
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
|
||||
}
|
||||
val list = describedType.described as List<*>
|
||||
return newInstance(listOf(list[0], Schema.get(list[1]!!)))
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = Envelope::class.java
|
||||
|
||||
override fun newInstance(described: Any?): Envelope {
|
||||
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
|
||||
return Envelope(list[0], list[1] as Schema)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDescriptor(): Any = DESCRIPTOR
|
||||
|
||||
override fun getDescribed(): Any = listOf(obj, schema)
|
||||
}
|
||||
|
||||
/**
|
||||
* This and the classes below are OO representations of the AMQP XML schema described in the specification. Their
|
||||
* [toString] representations generate the associated XML form.
|
||||
*/
|
||||
data class Schema(val types: List<TypeNotation>) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Schema> {
|
||||
val DESCRIPTOR = DescriptorRegistry.SCHEMA.amqpDescriptor
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.SCHEMA.amqpDescriptor
|
||||
|
||||
fun get(obj: Any): Schema {
|
||||
val describedType = obj as DescribedType
|
||||
@ -117,7 +58,7 @@ data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : Descr
|
||||
constructor(name: String?) : this(Symbol.valueOf(name))
|
||||
|
||||
companion object : DescribedTypeConstructor<Descriptor> {
|
||||
val DESCRIPTOR = DescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor
|
||||
|
||||
fun get(obj: Any): Descriptor {
|
||||
val describedType = obj as DescribedType
|
||||
@ -155,7 +96,7 @@ data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : Descr
|
||||
|
||||
data class Field(val name: String, val type: String, val requires: List<String>, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Field> {
|
||||
val DESCRIPTOR = DescriptorRegistry.FIELD.amqpDescriptor
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.FIELD.amqpDescriptor
|
||||
|
||||
fun get(obj: Any): Field {
|
||||
val describedType = obj as DescribedType
|
||||
@ -215,7 +156,7 @@ sealed class TypeNotation : DescribedType {
|
||||
|
||||
data class CompositeType(override val name: String, override val label: String?, override val provides: List<String>, override val descriptor: Descriptor, val fields: List<Field>) : TypeNotation() {
|
||||
companion object : DescribedTypeConstructor<CompositeType> {
|
||||
val DESCRIPTOR = DescriptorRegistry.COMPOSITE_TYPE.amqpDescriptor
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.COMPOSITE_TYPE.amqpDescriptor
|
||||
|
||||
fun get(describedType: DescribedType): CompositeType {
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
@ -264,7 +205,7 @@ data class RestrictedType(override val name: String,
|
||||
override val descriptor: Descriptor,
|
||||
val choices: List<Choice>) : TypeNotation() {
|
||||
companion object : DescribedTypeConstructor<RestrictedType> {
|
||||
val DESCRIPTOR = DescriptorRegistry.RESTRICTED_TYPE.amqpDescriptor
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.RESTRICTED_TYPE.amqpDescriptor
|
||||
|
||||
fun get(describedType: DescribedType): RestrictedType {
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
@ -309,7 +250,7 @@ data class RestrictedType(override val name: String,
|
||||
|
||||
data class Choice(val name: String, val value: String) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Choice> {
|
||||
val DESCRIPTOR = DescriptorRegistry.CHOICE.amqpDescriptor
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.CHOICE.amqpDescriptor
|
||||
|
||||
fun get(obj: Any): Choice {
|
||||
val describedType = obj as DescribedType
|
||||
@ -338,7 +279,7 @@ data class Choice(val name: String, val value: String) : DescribedType {
|
||||
|
||||
data class ReferencedObject(private val refCounter: Int) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<ReferencedObject> {
|
||||
val DESCRIPTOR = DescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor
|
||||
|
||||
fun get(obj: Any): ReferencedObject {
|
||||
val describedType = obj as DescribedType
|
||||
|
@ -45,10 +45,10 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
|
||||
val data = Data.Factory.create()
|
||||
data.withDescribed(Envelope.DESCRIPTOR_OBJECT) {
|
||||
withList {
|
||||
// Our object
|
||||
writeObject(obj, this)
|
||||
// The schema
|
||||
writeSchema(Schema(schemaHistory.toList()), this)
|
||||
val schema = Schema(schemaHistory.toList())
|
||||
writeSchema(schema, this)
|
||||
writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this)
|
||||
}
|
||||
}
|
||||
val bytes = ByteArray(data.encodedSize().toInt() + 8)
|
||||
@ -66,6 +66,10 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
|
||||
data.putObject(schema)
|
||||
}
|
||||
|
||||
open fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) {
|
||||
data.putObject(transformsSchema)
|
||||
}
|
||||
|
||||
internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type) {
|
||||
if (obj == null) {
|
||||
data.putNull()
|
||||
|
@ -34,6 +34,8 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
||||
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
|
||||
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
|
||||
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
||||
val classloader: ClassLoader
|
||||
get() = classCarpenter.classloader
|
||||
|
@ -0,0 +1,76 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import net.corda.core.serialization.CordaSerializationTransformRenames
|
||||
|
||||
/**
|
||||
* Utility class that defines an instance of a transform we support.
|
||||
*
|
||||
* @property type The transform annotation.
|
||||
* @property enum Maps the annotaiton onto a transform type, we expect there are multiple annotations that
|
||||
* would map to a single transform type.
|
||||
* @property getAnnotations Anonymous function that should return a list of Annotations encapsualted by the parent annotation
|
||||
* that reference the transform. Notionally this allows the code that extracts transforms to work on single instances
|
||||
* of a transform or a meta list of them.
|
||||
*/
|
||||
data class SupportedTransform(
|
||||
val type: Class<out Annotation>,
|
||||
val enum: TransformTypes,
|
||||
val getAnnotations: (Annotation) -> List<Annotation>)
|
||||
|
||||
/**
|
||||
* Extract from an annotated class the list of annotations that refer to a particular
|
||||
* transformation type when that class has multiple transforms wrapped in an
|
||||
* outer annotation
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private val wrapperExtract = { x: Annotation ->
|
||||
(x::class.java.getDeclaredMethod("value").invoke(x) as Array<Annotation>).toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract from an annotated class the list of annotations that refer to a particular
|
||||
* transformation type when that class has a single decorator applied
|
||||
*/
|
||||
private val singleExtract = { x: Annotation -> listOf(x) }
|
||||
|
||||
// Transform annotation used to test the handling of transforms the de-serialising node doesn't understand. At
|
||||
// some point test cases will have been created with this transform applied.
|
||||
// @Target(AnnotationTarget.CLASS)
|
||||
// @Retention(AnnotationRetention.RUNTIME)
|
||||
// annotation class UnknownTransformAnnotation(val a: Int, val b: Int, val c: Int)
|
||||
|
||||
/**
|
||||
* Utility list of all transforms we support that simplifies our generation code.
|
||||
*
|
||||
* NOTE: We have to support single instances of the transform annotations as well as the wrapping annotation
|
||||
* when many instances are repeated.
|
||||
*/
|
||||
val supportedTransforms = listOf(
|
||||
SupportedTransform(
|
||||
CordaSerializationTransformEnumDefaults::class.java,
|
||||
TransformTypes.EnumDefault,
|
||||
wrapperExtract
|
||||
),
|
||||
SupportedTransform(
|
||||
CordaSerializationTransformEnumDefault::class.java,
|
||||
TransformTypes.EnumDefault,
|
||||
singleExtract
|
||||
),
|
||||
SupportedTransform(
|
||||
CordaSerializationTransformRenames::class.java,
|
||||
TransformTypes.Rename,
|
||||
wrapperExtract
|
||||
),
|
||||
SupportedTransform(
|
||||
CordaSerializationTransformRename::class.java,
|
||||
TransformTypes.Rename,
|
||||
singleExtract
|
||||
)
|
||||
//,SupportedTransform(
|
||||
// UnknownTransformAnnotation::class.java,
|
||||
// TransformTypes.UnknownTest,
|
||||
// singleExtract)
|
||||
)
|
@ -0,0 +1,72 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.codec.DescribedTypeConstructor
|
||||
import java.io.NotSerializableException
|
||||
|
||||
/**
|
||||
* Enumerated type that represents each transform that can be applied to a class. Used as the key type in
|
||||
* the [TransformsSchema] map for each class.
|
||||
*
|
||||
* @property build should be a function that takes a transform [Annotation] (currently one of
|
||||
* [CordaSerializationTransformRename] or [CordaSerializationTransformEnumDefaults])
|
||||
* and constructs an instance of the corresponding [Transform] type
|
||||
*
|
||||
* DO NOT REORDER THE CONSTANTS!!! Please append any new entries to the end
|
||||
*/
|
||||
// TODO: it would be awesome to auto build this list by scanning for transform annotations themselves
|
||||
// TODO: annotated with some annotation
|
||||
enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType {
|
||||
/**
|
||||
* Placeholder entry for future transforms where a node receives a transform we've subsequently
|
||||
* added and thus the de-serialising node doesn't know about that transform.
|
||||
*/
|
||||
Unknown({ UnknownTransform() }) {
|
||||
override fun getDescriptor(): Any = DESCRIPTOR
|
||||
override fun getDescribed(): Any = ordinal
|
||||
},
|
||||
EnumDefault({ a -> EnumDefaultSchemaTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) {
|
||||
override fun getDescriptor(): Any = DESCRIPTOR
|
||||
override fun getDescribed(): Any = ordinal
|
||||
},
|
||||
Rename({ a -> RenameSchemaTransform((a as CordaSerializationTransformRename).from, a.to) }) {
|
||||
override fun getDescriptor(): Any = DESCRIPTOR
|
||||
override fun getDescribed(): Any = ordinal
|
||||
}
|
||||
// Transform used to test the unknown handler, leave this at as the final constant, uncomment
|
||||
// when regenerating test cases - if Java had a pre-processor this would be much neater
|
||||
//
|
||||
//,UnknownTest({ a -> UnknownTestTransform((a as UnknownTransformAnnotation).a, a.b, a.c)}) {
|
||||
// override fun getDescriptor(): Any = DESCRIPTOR
|
||||
// override fun getDescribed(): Any = ordinal
|
||||
//}
|
||||
;
|
||||
|
||||
companion object : DescribedTypeConstructor<TransformTypes> {
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor
|
||||
|
||||
/**
|
||||
* Used to construct an instance of the object from the serialised bytes
|
||||
*
|
||||
* @param obj the serialised byte object from the AMQP serialised stream
|
||||
*/
|
||||
override fun newInstance(obj: Any?): TransformTypes {
|
||||
val describedType = obj as DescribedType
|
||||
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
|
||||
}
|
||||
|
||||
return try {
|
||||
values()[describedType.described as Int]
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
values()[0]
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = TransformTypes::class.java
|
||||
}
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.codec.DescribedTypeConstructor
|
||||
import java.io.NotSerializableException
|
||||
import java.util.*
|
||||
|
||||
// NOTE: We are effectively going to replicate the annotations, we need to do this because
|
||||
// we can't instantiate instances of those annotation classes and this code needs to
|
||||
// work at the de-serialising end
|
||||
/**
|
||||
* Base class for representations of specific types of transforms as applied to a type within the
|
||||
* Corda serialisation framework
|
||||
*/
|
||||
abstract class Transform : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Transform> {
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT.amqpDescriptor
|
||||
|
||||
/**
|
||||
* @param obj: a serialized instance of a described type, should be one of the
|
||||
* descendants of this class
|
||||
*/
|
||||
private fun checkDescribed(obj: Any?): Any? {
|
||||
val describedType = obj as DescribedType
|
||||
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
|
||||
}
|
||||
|
||||
return describedType.described
|
||||
}
|
||||
|
||||
/**
|
||||
* From an encoded descendant return an instance of the specific type. Transforms are encoded into
|
||||
* the schema as a list of class name and parameters.Using the class name (list element 0)
|
||||
* create the appropriate class instance
|
||||
*
|
||||
* For future proofing any unknown transform types are not treated as errors, rather we
|
||||
* simply create a placeholder object so we can ignore it
|
||||
*
|
||||
* @param obj: a serialized instance of a described type, should be one of the
|
||||
* descendants of this class
|
||||
*/
|
||||
override fun newInstance(obj: Any?): Transform {
|
||||
val described = Transform.checkDescribed(obj) as List<*>
|
||||
return when (described[0]) {
|
||||
EnumDefaultSchemaTransform.typeName -> EnumDefaultSchemaTransform.newInstance(described)
|
||||
RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described)
|
||||
else -> UnknownTransform()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = Transform::class.java
|
||||
}
|
||||
|
||||
override fun getDescriptor(): Any = DESCRIPTOR
|
||||
|
||||
/**
|
||||
* Return a string representation of a transform in terms of key / value pairs, used
|
||||
* by the serializer to encode arbitrary transforms
|
||||
*/
|
||||
abstract fun params(): String
|
||||
|
||||
abstract val name: String
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform type placeholder that allows for backward compatibility. Should a noce recieve
|
||||
* a transform type it doesn't recognise, we can will use this as a placeholder
|
||||
*/
|
||||
class UnknownTransform : Transform() {
|
||||
companion object : DescribedTypeConstructor<UnknownTransform> {
|
||||
val typeName = "UnknownTransform"
|
||||
|
||||
override fun newInstance(obj: Any?) = UnknownTransform()
|
||||
|
||||
override fun getTypeClass(): Class<*> = UnknownTransform::class.java
|
||||
}
|
||||
|
||||
override fun getDescribed(): Any = emptyList<Any>()
|
||||
override fun params() = ""
|
||||
|
||||
override val name: String get() = typeName
|
||||
}
|
||||
|
||||
class UnknownTestTransform(val a: Int, val b: Int, val c: Int) : Transform() {
|
||||
companion object : DescribedTypeConstructor<UnknownTestTransform> {
|
||||
val typeName = "UnknownTest"
|
||||
|
||||
override fun newInstance(obj: Any?) : UnknownTestTransform {
|
||||
val described = obj as List<*>
|
||||
return UnknownTestTransform(described[1] as Int, described[2] as Int, described[3] as Int)
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = UnknownTransform::class.java
|
||||
}
|
||||
|
||||
override fun getDescribed(): Any = listOf(name, a, b, c)
|
||||
override fun params() = ""
|
||||
|
||||
override val name: String get() = typeName
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform to be used on an Enumerated Type whenever a new element is added
|
||||
*
|
||||
* @property old The value the [new] instance should default to when not available
|
||||
* @property new the value (as a String) that has been added
|
||||
*/
|
||||
class EnumDefaultSchemaTransform(val old: String, val new: String) : Transform() {
|
||||
companion object : DescribedTypeConstructor<EnumDefaultSchemaTransform> {
|
||||
/**
|
||||
* Value encoded into the schema that identifies a transform as this type
|
||||
*/
|
||||
val typeName = "EnumDefault"
|
||||
|
||||
override fun newInstance(obj: Any?): EnumDefaultSchemaTransform {
|
||||
val described = obj as List<*>
|
||||
val old = described[1] as? String ?: throw IllegalStateException("Was expecting \"old\" as a String")
|
||||
val new = described[2] as? String ?: throw IllegalStateException("Was expecting \"new\" as a String")
|
||||
return EnumDefaultSchemaTransform(old, new)
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = EnumDefaultSchemaTransform::class.java
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
constructor (annotation: CordaSerializationTransformEnumDefault) : this(annotation.old, annotation.new)
|
||||
|
||||
override fun getDescribed(): Any = listOf(name, old, new)
|
||||
override fun params() = "old=${old.esc()} new=${new.esc()}"
|
||||
|
||||
override fun equals(other: Any?) = (
|
||||
(other is EnumDefaultSchemaTransform && other.new == new && other.old == old) || super.equals(other))
|
||||
|
||||
override fun hashCode() = (17 * new.hashCode()) + old.hashCode()
|
||||
|
||||
override val name: String get() = typeName
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform applied to either a class or enum where a property is renamed
|
||||
*
|
||||
* @property from the name at time of change of the property
|
||||
* @property to the new name of the property
|
||||
*/
|
||||
class RenameSchemaTransform(val from: String, val to: String) : Transform() {
|
||||
companion object : DescribedTypeConstructor<RenameSchemaTransform> {
|
||||
/**
|
||||
* Value encoded into the schema that identifies a transform as this type
|
||||
*/
|
||||
val typeName = "Rename"
|
||||
|
||||
override fun newInstance(obj: Any?): RenameSchemaTransform {
|
||||
val described = obj as List<*>
|
||||
val from = described[1] as? String ?: throw IllegalStateException("Was expecting \"from\" as a String")
|
||||
val to = described[2] as? String ?: throw IllegalStateException("Was expecting \"to\" as a String")
|
||||
return RenameSchemaTransform(from, to)
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = RenameSchemaTransform::class.java
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
constructor (annotation: CordaSerializationTransformRename) : this(annotation.from, annotation.to)
|
||||
|
||||
override fun getDescribed(): Any = listOf(name, from, to)
|
||||
|
||||
override fun params() = "from=${from.esc()} to=${to.esc()}"
|
||||
|
||||
override fun equals(other: Any?) = (
|
||||
(other is RenameSchemaTransform && other.from == from && other.to == to) || super.equals(other))
|
||||
|
||||
override fun hashCode() = (11 * from.hashCode()) + to.hashCode()
|
||||
|
||||
override val name: String get() = typeName
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the set of all transforms that can be a applied to all classes represented as part of
|
||||
* an AMQP schema. It forms a part of the AMQP envelope alongside the [Schema] and the serialized bytes
|
||||
*
|
||||
* @property types maps class names to a map of transformation types. In turn those transformation types
|
||||
* are each a list of instances o that transform.
|
||||
*/
|
||||
data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, MutableList<Transform>>>) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<TransformsSchema> {
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_SCHEMA.amqpDescriptor
|
||||
|
||||
/**
|
||||
* Prepare a schema for encoding, takes all of the types being transmitted and inspects each
|
||||
* one for any transform annotations. If there are any build up a set that can be
|
||||
* encoded into the AMQP [Envelope]
|
||||
*
|
||||
* @param schema should be a [Schema] generated for a serialised data structure
|
||||
* @param sf should be provided by the same serialization context that generated the schema
|
||||
*/
|
||||
fun build(schema: Schema, sf: SerializerFactory): TransformsSchema {
|
||||
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
|
||||
schema.types.forEach { type ->
|
||||
sf.transformsCache.computeIfAbsent(type.name) {
|
||||
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
|
||||
try {
|
||||
val clazz = sf.classloader.loadClass(type.name)
|
||||
|
||||
supportedTransforms.forEach { transform ->
|
||||
clazz.getAnnotation(transform.type)?.let { list ->
|
||||
transform.getAnnotations(list).forEach { annotation ->
|
||||
val t = transform.enum.build(annotation)
|
||||
|
||||
// we're explicitly rejecting repeated annotations, whilst it's fine and we'd just
|
||||
// ignore them it feels like a good thing to alert the user to since this is
|
||||
// more than likely a typo in their code so best make it an actual error
|
||||
if (transforms.computeIfAbsent(transform.enum) { mutableListOf() }
|
||||
.filter { t == it }.isNotEmpty()) {
|
||||
throw NotSerializableException(
|
||||
"Repeated unique transformation annotation of type ${t.name}")
|
||||
}
|
||||
|
||||
transforms[transform.enum]!!.add(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_: ClassNotFoundException) {
|
||||
// if we can't load the class we'll end up caching an empty list which is fine as that
|
||||
// list, on lookup, won't be included in the schema because it's empty
|
||||
}
|
||||
|
||||
transforms
|
||||
}.apply {
|
||||
if (isNotEmpty()) {
|
||||
rtn[type.name] = this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TransformsSchema(rtn)
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = TransformsSchema::class.java
|
||||
|
||||
/**
|
||||
* Constructs an instance of the object from the serialised form of an instance
|
||||
* of this object
|
||||
*/
|
||||
override fun newInstance(described: Any?): TransformsSchema {
|
||||
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
val describedType = described as? DescribedType ?: return TransformsSchema(rtn)
|
||||
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
|
||||
}
|
||||
|
||||
val map = describedType.described as? Map<*, *> ?:
|
||||
throw NotSerializableException("Transform schema must be encoded as a map")
|
||||
|
||||
map.forEach { type ->
|
||||
val fingerprint = type.key as? String ?:
|
||||
throw NotSerializableException("Fingerprint must be encoded as a string")
|
||||
|
||||
rtn[fingerprint] = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
|
||||
|
||||
(type.value as Map<*, *>).forEach { transformType, transforms ->
|
||||
val transform = TransformTypes.newInstance(transformType)
|
||||
|
||||
rtn[fingerprint]!![transform] = mutableListOf()
|
||||
(transforms as List<*>).forEach {
|
||||
rtn[fingerprint]!![TransformTypes.newInstance(transformType)]?.add(Transform.newInstance(it)) ?:
|
||||
throw NotSerializableException("De-serialization error with transform for class "
|
||||
+ "${type.key} ${transform.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TransformsSchema(rtn)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDescriptor(): Any = DESCRIPTOR
|
||||
|
||||
override fun getDescribed(): Any = types
|
||||
|
||||
override fun toString(): String {
|
||||
data class Indent(val indent: String) {
|
||||
@Suppress("UNUSED") constructor(i: Indent) : this(" ${i.indent}")
|
||||
|
||||
override fun toString() = indent
|
||||
}
|
||||
|
||||
val sb = StringBuilder("")
|
||||
val indent = Indent("")
|
||||
|
||||
sb.appendln("$indent<type-transforms>")
|
||||
types.forEach { type ->
|
||||
val indent = Indent(indent)
|
||||
sb.appendln("$indent<type name=${type.key.esc()}>")
|
||||
type.value.forEach { transform ->
|
||||
val indent = Indent(indent)
|
||||
sb.appendln("$indent<transforms type=${transform.key.name.esc()}>")
|
||||
transform.value.forEach {
|
||||
val indent = Indent(indent)
|
||||
sb.appendln("$indent<transform ${it.params()} />")
|
||||
}
|
||||
sb.appendln("$indent</transforms>")
|
||||
}
|
||||
sb.appendln("$indent</type>")
|
||||
}
|
||||
sb.appendln("$indent</type-transforms>")
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.esc() = "\"$this\""
|
@ -2,11 +2,11 @@ package net.corda.nodeapi.internal.serialization;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import net.corda.core.serialization.SerializationContext;
|
||||
import net.corda.core.serialization.SerializationDefaults;
|
||||
import net.corda.core.serialization.SerializationFactory;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.testing.TestDependencyInjectionBase;
|
||||
import net.corda.testing.SerializationEnvironmentRule;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -16,13 +16,14 @@ import java.util.concurrent.Callable;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
|
||||
|
||||
public final class ForbiddenLambdaSerializationTests extends TestDependencyInjectionBase {
|
||||
|
||||
public final class ForbiddenLambdaSerializationTests {
|
||||
@Rule
|
||||
public SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||
private SerializationFactory factory;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
factory = SerializationDefaults.INSTANCE.getSERIALIZATION_FACTORY();
|
||||
factory = testSerialization.env.getSERIALIZATION_FACTORY();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -2,11 +2,11 @@ package net.corda.nodeapi.internal.serialization;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import net.corda.core.serialization.SerializationContext;
|
||||
import net.corda.core.serialization.SerializationDefaults;
|
||||
import net.corda.core.serialization.SerializationFactory;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.testing.TestDependencyInjectionBase;
|
||||
import net.corda.testing.SerializationEnvironmentRule;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -15,14 +15,15 @@ import java.util.concurrent.Callable;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
|
||||
|
||||
public final class LambdaCheckpointSerializationTest extends TestDependencyInjectionBase {
|
||||
|
||||
public final class LambdaCheckpointSerializationTest {
|
||||
@Rule
|
||||
public SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||
private SerializationFactory factory;
|
||||
private SerializationContext context;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
factory = SerializationDefaults.INSTANCE.getSERIALIZATION_FACTORY();
|
||||
factory = testSerialization.env.getSERIALIZATION_FACTORY();
|
||||
context = new SerializationContextImpl(SerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint);
|
||||
}
|
||||
|
||||
|
@ -186,6 +186,8 @@ public class JavaSerializationOutputTests {
|
||||
decoder.register(CompositeType.Companion.getDESCRIPTOR(), CompositeType.Companion);
|
||||
decoder.register(Choice.Companion.getDESCRIPTOR(), Choice.Companion);
|
||||
decoder.register(RestrictedType.Companion.getDESCRIPTOR(), RestrictedType.Companion);
|
||||
decoder.register(Transform.Companion.getDESCRIPTOR(), Transform.Companion);
|
||||
decoder.register(TransformsSchema.Companion.getDESCRIPTOR(), TransformsSchema.Companion);
|
||||
|
||||
new EncoderImpl(decoder);
|
||||
decoder.setByteBuffer(ByteBuffer.wrap(bytes.getBytes(), 8, bytes.getSize() - 8));
|
||||
|
@ -11,9 +11,13 @@ import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AttachmentsClassLoaderStaticContractTests : TestDependencyInjectionBase() {
|
||||
class AttachmentsClassLoaderStaticContractTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
class AttachmentDummyContract : Contract {
|
||||
companion object {
|
||||
|
@ -23,6 +23,7 @@ import net.corda.testing.node.MockServices
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@ -32,7 +33,7 @@ import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
|
||||
class AttachmentsClassLoaderTests {
|
||||
companion object {
|
||||
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar")
|
||||
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||
@ -48,6 +49,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private lateinit var serviceHub: DummyServiceHub
|
||||
|
||||
class DummyServiceHub : MockServices() {
|
||||
|
@ -14,7 +14,7 @@ import net.corda.core.utilities.sequence
|
||||
import net.corda.node.serialization.KryoServerSerializationScheme
|
||||
import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.testing.ALICE_PUBKEY
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Before
|
||||
@ -28,7 +28,10 @@ import java.time.Instant
|
||||
import java.util.Collections
|
||||
import kotlin.test.*
|
||||
|
||||
class KryoTests : TestDependencyInjectionBase() {
|
||||
class KryoTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private lateinit var factory: SerializationFactory
|
||||
private lateinit var context: SerializationContext
|
||||
|
||||
|
@ -7,19 +7,20 @@ import net.corda.node.services.statemachine.SessionData
|
||||
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
||||
import net.corda.nodeapi.internal.serialization.amqp.Envelope
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.amqpSpecific
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.NotSerializableException
|
||||
import java.nio.charset.StandardCharsets.US_ASCII
|
||||
import java.util.*
|
||||
|
||||
class ListsSerializationTest : TestDependencyInjectionBase() {
|
||||
class ListsSerializationTest {
|
||||
private companion object {
|
||||
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
|
||||
|
||||
@ -31,6 +32,10 @@ class ListsSerializationTest : TestDependencyInjectionBase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `check list can be serialized as root of serialization graph`() {
|
||||
assertEqualAfterRoundTripSerialization(emptyList<Int>())
|
||||
|
@ -6,23 +6,28 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.statemachine.SessionData
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.amqpSpecific
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class MapsSerializationTest : TestDependencyInjectionBase() {
|
||||
class MapsSerializationTest {
|
||||
private companion object {
|
||||
val javaEmptyMapClass = Collections.emptyMap<Any, Any>().javaClass
|
||||
val smallMap = mapOf("foo" to "bar", "buzz" to "bull")
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `check EmptyMap serialization`() = amqpSpecific("kotlin.collections.EmptyMap is not enabled for Kryo serialization") {
|
||||
assertEqualAfterRoundTripSerialization(emptyMap<Any, Any>())
|
||||
|
@ -4,8 +4,9 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.serialization.SerializationContext.UseCase.*
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
@ -13,8 +14,7 @@ import java.security.PrivateKey
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class PrivateKeySerializationTest(private val privateKey: PrivateKey, private val testName: String) : TestDependencyInjectionBase() {
|
||||
|
||||
class PrivateKeySerializationTest(private val privateKey: PrivateKey, private val testName: String) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
@ -26,6 +26,10 @@ class PrivateKeySerializationTest(private val privateKey: PrivateKey, private va
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `passed with expected UseCases`() {
|
||||
assertTrue { privateKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes.isNotEmpty() }
|
||||
|
@ -5,22 +5,25 @@ import com.esotericsoftware.kryo.KryoException
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class SerializationTokenTest : TestDependencyInjectionBase() {
|
||||
|
||||
class SerializationTokenTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private lateinit var factory: SerializationFactory
|
||||
private lateinit var context: SerializationContext
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
factory = SerializationDefaults.SERIALIZATION_FACTORY
|
||||
context = SerializationDefaults.CHECKPOINT_CONTEXT.withWhitelisted(SingletonSerializationToken::class.java)
|
||||
factory = testSerialization.env.SERIALIZATION_FACTORY
|
||||
context = testSerialization.env.CHECKPOINT_CONTEXT.withWhitelisted(SingletonSerializationToken::class.java)
|
||||
}
|
||||
|
||||
// Large tokenizable object so we can tell from the smaller number of serialized bytes it was actually tokenized
|
||||
|
@ -5,19 +5,24 @@ import com.esotericsoftware.kryo.util.DefaultClassResolver
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.statemachine.SessionData
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.*
|
||||
|
||||
class SetsSerializationTest : TestDependencyInjectionBase() {
|
||||
class SetsSerializationTest {
|
||||
private companion object {
|
||||
val javaEmptySetClass = Collections.emptySet<Any>().javaClass
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `check set can be serialized as root of serialization graph`() {
|
||||
assertEqualAfterRoundTripSerialization(emptySet<Int>())
|
||||
|
@ -18,18 +18,31 @@ class TestSerializationOutput(
|
||||
if (verbose) println(schema)
|
||||
super.writeSchema(schema, data)
|
||||
}
|
||||
|
||||
override fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) {
|
||||
if(verbose) {
|
||||
println ("Writing Transform Schema")
|
||||
println (transformsSchema)
|
||||
}
|
||||
super.writeTransformSchema(transformsSchema, data)
|
||||
}
|
||||
}
|
||||
|
||||
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
||||
|
||||
data class BytesAndSchema<T : Any>(val obj: SerializedBytes<T>, val schema: Schema)
|
||||
data class BytesAndSchemas<T : Any>(
|
||||
val obj: SerializedBytes<T>,
|
||||
val schema: Schema,
|
||||
val transformsSchema: TransformsSchema)
|
||||
|
||||
// Extension for the serialize routine that returns the scheme encoded into the
|
||||
// bytes as well as the bytes for simple testing
|
||||
@Throws(NotSerializableException::class)
|
||||
fun <T : Any> SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchema<T> {
|
||||
fun <T : Any> SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchemas<T> {
|
||||
try {
|
||||
return BytesAndSchema(_serialize(obj), Schema(schemaHistory.toList()))
|
||||
val blob = _serialize(obj)
|
||||
val schema = Schema(schemaHistory.toList())
|
||||
return BytesAndSchemas(blob, schema, TransformsSchema.build(schema, serializerFactory))
|
||||
} finally {
|
||||
andFinally()
|
||||
}
|
||||
|
@ -0,0 +1,436 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.serialization.*
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.NotSerializableException
|
||||
import java.net.URI
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class EnumEvolvabilityTests {
|
||||
var localPath = "file:///home/katelyn/srcs/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp"
|
||||
|
||||
|
||||
companion object {
|
||||
val VERBOSE = false
|
||||
}
|
||||
|
||||
enum class NotAnnotated {
|
||||
A, B, C, D
|
||||
}
|
||||
|
||||
@CordaSerializationTransformEnumDefaults()
|
||||
enum class MissingDefaults {
|
||||
A, B, C, D
|
||||
}
|
||||
|
||||
@CordaSerializationTransformRenames()
|
||||
enum class MissingRenames {
|
||||
A, B, C, D
|
||||
}
|
||||
|
||||
@CordaSerializationTransformEnumDefault("D", "A")
|
||||
enum class AnnotatedEnumOnce {
|
||||
A, B, C, D
|
||||
}
|
||||
|
||||
@CordaSerializationTransformEnumDefaults(
|
||||
CordaSerializationTransformEnumDefault("E", "D"),
|
||||
CordaSerializationTransformEnumDefault("D", "A"))
|
||||
enum class AnnotatedEnumTwice {
|
||||
A, B, C, D, E
|
||||
}
|
||||
|
||||
@CordaSerializationTransformRename("E", "D")
|
||||
enum class RenameEnumOnce {
|
||||
A, B, C, E
|
||||
}
|
||||
|
||||
@CordaSerializationTransformRenames(
|
||||
CordaSerializationTransformRename("E", "C"),
|
||||
CordaSerializationTransformRename("F", "D"))
|
||||
enum class RenameEnumTwice {
|
||||
A, B, E, F
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noAnnotation() {
|
||||
data class C (val n: NotAnnotated)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(NotAnnotated.A))
|
||||
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(0, bAndS.transformsSchema.types.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingDefaults() {
|
||||
data class C (val m: MissingDefaults)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingDefaults.A))
|
||||
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(0, bAndS.transformsSchema.types.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingRenames() {
|
||||
data class C (val m: MissingRenames)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingRenames.A))
|
||||
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(0, bAndS.transformsSchema.types.size)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun defaultAnnotationIsAddedToEnvelope() {
|
||||
data class C (val annotatedEnum: AnnotatedEnumOnce)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumOnce.D))
|
||||
|
||||
// only the enum is decorated so schema sizes should be different (2 objects, only one evolved)
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(1, bAndS.transformsSchema.types.size)
|
||||
assertEquals (AnnotatedEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first())
|
||||
|
||||
val schema = bAndS.transformsSchema.types.values.first()
|
||||
|
||||
assertEquals(1, schema.size)
|
||||
assertTrue (schema.keys.contains(TransformTypes.EnumDefault))
|
||||
assertEquals (1, schema[TransformTypes.EnumDefault]!!.size)
|
||||
assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
|
||||
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
|
||||
assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleDefaultAnnotationIsAddedToEnvelope() {
|
||||
data class C (val annotatedEnum: AnnotatedEnumTwice)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumTwice.E))
|
||||
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(1, bAndS.transformsSchema.types.size)
|
||||
assertEquals (AnnotatedEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first())
|
||||
|
||||
val schema = bAndS.transformsSchema.types.values.first()
|
||||
|
||||
assertEquals(1, schema.size)
|
||||
assertTrue (schema.keys.contains(TransformTypes.EnumDefault))
|
||||
assertEquals (2, schema[TransformTypes.EnumDefault]!!.size)
|
||||
assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
|
||||
assertEquals ("E", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
|
||||
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
|
||||
assertTrue (schema[TransformTypes.EnumDefault]!![1] is EnumDefaultSchemaTransform)
|
||||
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).new)
|
||||
assertEquals ("A", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).old)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun defaultAnnotationIsAddedToEnvelopeAndDeserialised() {
|
||||
data class C (val annotatedEnum: AnnotatedEnumOnce)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumOnce.D))
|
||||
val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb)
|
||||
|
||||
// as with the serialisation stage, de-serialising the object we should see two
|
||||
// types described in the header with one of those having transforms
|
||||
assertEquals(2, db.envelope.schema.types.size)
|
||||
assertEquals(1, db.envelope.transformsSchema.types.size)
|
||||
|
||||
val eName = AnnotatedEnumOnce::class.java.name
|
||||
val types = db.envelope.schema.types
|
||||
val transforms = db.envelope.transformsSchema.types
|
||||
|
||||
assertEquals(1, types.filter { it.name == eName }.size)
|
||||
assertTrue(eName in transforms)
|
||||
|
||||
val schema = transforms[eName]
|
||||
|
||||
assertTrue (schema!!.keys.contains(TransformTypes.EnumDefault))
|
||||
assertEquals (1, schema[TransformTypes.EnumDefault]!!.size)
|
||||
assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
|
||||
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
|
||||
assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleDefaultAnnotationIsAddedToEnvelopeAndDeserialised() {
|
||||
data class C(val annotatedEnum: AnnotatedEnumTwice)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumTwice.E))
|
||||
val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb)
|
||||
|
||||
// as with the serialisation stage, de-serialising the object we should see two
|
||||
// types described in the header with one of those having transforms
|
||||
assertEquals(2, db.envelope.schema.types.size)
|
||||
assertEquals(1, db.envelope.transformsSchema.types.size)
|
||||
|
||||
val transforms = db.envelope.transformsSchema.types
|
||||
|
||||
assertTrue (transforms.contains(AnnotatedEnumTwice::class.java.name))
|
||||
assertTrue (transforms[AnnotatedEnumTwice::class.java.name]!!.contains(TransformTypes.EnumDefault))
|
||||
assertEquals (2, transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!!.size)
|
||||
|
||||
val enumDefaults = transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!!
|
||||
|
||||
assertEquals("E", (enumDefaults[0] as EnumDefaultSchemaTransform).new)
|
||||
assertEquals("D", (enumDefaults[0] as EnumDefaultSchemaTransform).old)
|
||||
assertEquals("D", (enumDefaults[1] as EnumDefaultSchemaTransform).new)
|
||||
assertEquals("A", (enumDefaults[1] as EnumDefaultSchemaTransform).old)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renameAnnotationIsAdded() {
|
||||
data class C (val annotatedEnum: RenameEnumOnce)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
// Serialise the object
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumOnce.E))
|
||||
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(1, bAndS.transformsSchema.types.size)
|
||||
assertEquals (RenameEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first())
|
||||
|
||||
val serialisedSchema = bAndS.transformsSchema.types[RenameEnumOnce::class.java.name]!!
|
||||
|
||||
assertEquals(1, serialisedSchema.size)
|
||||
assertTrue(serialisedSchema.containsKey(TransformTypes.Rename))
|
||||
assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size)
|
||||
assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
|
||||
assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
|
||||
|
||||
// Now de-serialise the blob
|
||||
val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj)
|
||||
|
||||
assertEquals(2, cAndS.envelope.schema.types.size)
|
||||
assertEquals(1, cAndS.envelope.transformsSchema.types.size)
|
||||
assertEquals (RenameEnumOnce::class.java.name, cAndS.envelope.transformsSchema.types.keys.first())
|
||||
|
||||
val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumOnce::class.java.name]!!
|
||||
|
||||
assertEquals(1, deserialisedSchema.size)
|
||||
assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename))
|
||||
assertEquals(1, deserialisedSchema[TransformTypes.Rename]!!.size)
|
||||
assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
|
||||
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleRenameAnnotationIsAdded() {
|
||||
data class C (val annotatedEnum: RenameEnumTwice)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
// Serialise the object
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumTwice.F))
|
||||
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(1, bAndS.transformsSchema.types.size)
|
||||
assertEquals (RenameEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first())
|
||||
|
||||
val serialisedSchema = bAndS.transformsSchema.types[RenameEnumTwice::class.java.name]!!
|
||||
|
||||
assertEquals(1, serialisedSchema.size)
|
||||
assertTrue(serialisedSchema.containsKey(TransformTypes.Rename))
|
||||
assertEquals(2, serialisedSchema[TransformTypes.Rename]!!.size)
|
||||
assertEquals("C", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
|
||||
assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
|
||||
assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from)
|
||||
assertEquals("F", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to)
|
||||
|
||||
// Now de-serialise the blob
|
||||
val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj)
|
||||
|
||||
assertEquals(2, cAndS.envelope.schema.types.size)
|
||||
assertEquals(1, cAndS.envelope.transformsSchema.types.size)
|
||||
assertEquals (RenameEnumTwice::class.java.name, cAndS.envelope.transformsSchema.types.keys.first())
|
||||
|
||||
val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumTwice::class.java.name]!!
|
||||
|
||||
assertEquals(1, deserialisedSchema.size)
|
||||
assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename))
|
||||
assertEquals(2, deserialisedSchema[TransformTypes.Rename]!!.size)
|
||||
assertEquals("C", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
|
||||
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
|
||||
assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from)
|
||||
assertEquals("F", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to)
|
||||
}
|
||||
|
||||
@CordaSerializationTransformRename(from="A", to="X")
|
||||
@CordaSerializationTransformEnumDefault(old = "X", new="E")
|
||||
enum class RenameAndExtendEnum {
|
||||
X, B, C, D, E
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bothAnnotationTypes() {
|
||||
data class C (val annotatedEnum: RenameAndExtendEnum)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
// Serialise the object
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameAndExtendEnum.X))
|
||||
|
||||
assertEquals(2, bAndS.schema.types.size)
|
||||
assertEquals(1, bAndS.transformsSchema.types.size)
|
||||
assertEquals (RenameAndExtendEnum::class.java.name, bAndS.transformsSchema.types.keys.first())
|
||||
|
||||
val serialisedSchema = bAndS.transformsSchema.types[RenameAndExtendEnum::class.java.name]!!
|
||||
|
||||
// This time there should be two distinct transform types (all previous tests have had only
|
||||
// a single type
|
||||
assertEquals(2, serialisedSchema.size)
|
||||
assertTrue (serialisedSchema.containsKey(TransformTypes.Rename))
|
||||
assertTrue (serialisedSchema.containsKey(TransformTypes.EnumDefault))
|
||||
|
||||
assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size)
|
||||
assertEquals("A", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
|
||||
assertEquals("X", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
|
||||
|
||||
assertEquals(1, serialisedSchema[TransformTypes.EnumDefault]!!.size)
|
||||
assertEquals("E", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
|
||||
assertEquals("X", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
|
||||
}
|
||||
|
||||
@CordaSerializationTransformEnumDefaults (
|
||||
CordaSerializationTransformEnumDefault("D", "A"),
|
||||
CordaSerializationTransformEnumDefault("D", "A"))
|
||||
enum class RepeatedAnnotation {
|
||||
A, B, C, D, E
|
||||
}
|
||||
|
||||
@Test
|
||||
fun repeatedAnnotation() {
|
||||
data class C (val a: RepeatedAnnotation)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
Assertions.assertThatThrownBy {
|
||||
TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RepeatedAnnotation.A))
|
||||
}.isInstanceOf(NotSerializableException::class.java)
|
||||
}
|
||||
|
||||
@CordaSerializationTransformEnumDefault("D", "A")
|
||||
enum class E1 {
|
||||
A, B, C, D
|
||||
}
|
||||
|
||||
@CordaSerializationTransformEnumDefaults (
|
||||
CordaSerializationTransformEnumDefault("D", "A"),
|
||||
CordaSerializationTransformEnumDefault("E", "A"))
|
||||
enum class E2 {
|
||||
A, B, C, D, E
|
||||
}
|
||||
|
||||
@CordaSerializationTransformEnumDefaults (CordaSerializationTransformEnumDefault("D", "A"))
|
||||
enum class E3 {
|
||||
A, B, C, D
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiEnums() {
|
||||
data class A (val a: E1, val b: E2)
|
||||
data class B (val a: E3, val b: A, val c: E1)
|
||||
data class C (val a: B, val b: E2, val c: E3)
|
||||
|
||||
val c = C(B(E3.A,A(E1.A,E2.B),E1.C),E2.B,E3.A)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
// Serialise the object
|
||||
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c)
|
||||
|
||||
println (bAndS.transformsSchema)
|
||||
|
||||
// we have six types and three of those, the enums, should have transforms
|
||||
assertEquals(6, bAndS.schema.types.size)
|
||||
assertEquals(3, bAndS.transformsSchema.types.size)
|
||||
|
||||
assertTrue (E1::class.java.name in bAndS.transformsSchema.types)
|
||||
assertTrue (E2::class.java.name in bAndS.transformsSchema.types)
|
||||
assertTrue (E3::class.java.name in bAndS.transformsSchema.types)
|
||||
|
||||
val e1S = bAndS.transformsSchema.types[E1::class.java.name]!!
|
||||
val e2S = bAndS.transformsSchema.types[E2::class.java.name]!!
|
||||
val e3S = bAndS.transformsSchema.types[E3::class.java.name]!!
|
||||
|
||||
assertEquals(1, e1S.size)
|
||||
assertEquals(1, e2S.size)
|
||||
assertEquals(1, e3S.size)
|
||||
|
||||
assertTrue(TransformTypes.EnumDefault in e1S)
|
||||
assertTrue(TransformTypes.EnumDefault in e2S)
|
||||
assertTrue(TransformTypes.EnumDefault in e3S)
|
||||
|
||||
assertEquals(1, e1S[TransformTypes.EnumDefault]!!.size)
|
||||
assertEquals(2, e2S[TransformTypes.EnumDefault]!!.size)
|
||||
assertEquals(1, e3S[TransformTypes.EnumDefault]!!.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCache() {
|
||||
data class C2(val annotatedEnum: AnnotatedEnumOnce)
|
||||
data class C1(val annotatedEnum: AnnotatedEnumOnce)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
assertEquals(0, sf.transformsCache.size)
|
||||
|
||||
val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D))
|
||||
|
||||
assertEquals(2, sf.transformsCache.size)
|
||||
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
|
||||
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
||||
|
||||
val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D))
|
||||
|
||||
assertEquals(3, sf.transformsCache.size)
|
||||
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
|
||||
assertTrue(sf.transformsCache.containsKey(C2::class.java.name))
|
||||
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
||||
|
||||
assertEquals (sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name],
|
||||
sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name])
|
||||
}
|
||||
|
||||
|
||||
//@UnknownTransformAnnotation (10, 20, 30)
|
||||
enum class WithUnknownTest {
|
||||
A, B, C, D
|
||||
}
|
||||
|
||||
data class WrapsUnknown(val unknown: WithUnknownTest)
|
||||
|
||||
// To regenerate the types for this test uncomment the UnknownTransformAnnotation from
|
||||
// TransformTypes.kt and SupportedTransforms.kt
|
||||
// ALSO: remember to re-annotate the enum WithUnkownTest above
|
||||
@Test
|
||||
fun testUnknownTransform() {
|
||||
val resource = "EnumEvolvabilityTests.testUnknownTransform"
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
//File(URI("$localPath/$resource")).writeBytes(
|
||||
// SerializationOutput(sf).serialize(WrapsUnknown(WithUnknownTest.D)).bytes)
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val sb1 = File(path.toURI()).readBytes()
|
||||
|
||||
val envelope = DeserializationInput(sf).deserializeAndReturnEnvelope(SerializedBytes<WrapsUnknown>(sb1)).envelope
|
||||
|
||||
assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name))
|
||||
assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown))
|
||||
}
|
||||
}
|
@ -5,36 +5,40 @@ import net.corda.core.serialization.SerializedBytes
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.NotSerializableException
|
||||
import java.net.URI
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
// To regenerate any of the binary test files do the following
|
||||
//
|
||||
// 0. set localPath accordingly
|
||||
// 1. Uncomment the code where the original form of the class is defined in the test
|
||||
// 2. Comment out the rest of the test
|
||||
// 3. Run the test
|
||||
// 4. Using the printed path copy that file to the resources directory
|
||||
// 5. Comment back out the generation code and uncomment the actual test
|
||||
class EvolvabilityTests {
|
||||
// When regenerating the test files this needs to be set to the file system location of the resource files
|
||||
var localPath = "file:///<path>/<to>/<toplevel of>/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp"
|
||||
|
||||
@Test
|
||||
fun simpleOrderSwapSameType() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapSameType")
|
||||
val f = File(path.toURI())
|
||||
val resource= "EvolvabilityTests.simpleOrderSwapSameType"
|
||||
|
||||
val A = 1
|
||||
val B = 2
|
||||
|
||||
// Original version of the class for the serialised version of this class
|
||||
//
|
||||
// data class C (val a: Int, val b: Int)
|
||||
// val sc = SerializationOutput(sf).serialize(C(A, B))
|
||||
// f.writeBytes(sc.bytes)
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes)
|
||||
// println (path)
|
||||
|
||||
// new version of the class, in this case the order of the parameters has been swapped
|
||||
data class C(val b: Int, val a: Int)
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
|
||||
|
||||
@ -45,21 +49,20 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun simpleOrderSwapDifferentType() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapDifferentType")
|
||||
val f = File(path.toURI())
|
||||
val A = 1
|
||||
val B = "two"
|
||||
val resource = "EvolvabilityTests.simpleOrderSwapDifferentType"
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class C (val a: Int, val b: String)
|
||||
// val sc = SerializationOutput(sf).serialize(C(A, B))
|
||||
// f.writeBytes(sc.bytes)
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes)
|
||||
// println (path)
|
||||
|
||||
// new version of the class, in this case the order of the parameters has been swapped
|
||||
data class C(val b: String, val a: Int)
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
|
||||
|
||||
@ -70,24 +73,23 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun addAdditionalParamNotMandatory() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAdditionalParamNotMandatory")
|
||||
val f = File(path.toURI())
|
||||
val A = 1
|
||||
val resource = "EvolvabilityTests.addAdditionalParamNotMandatory"
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class C(val a: Int)
|
||||
// val sc = SerializationOutput(sf).serialize(C(A))
|
||||
// f.writeBytes(sc.bytes)
|
||||
// println ("Path = $path")
|
||||
// File(URI("$localPath/$resource")).writeBytes( SerializationOutput(sf).serialize(C(A))
|
||||
|
||||
data class C(val a: Int, val b: Int?)
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
|
||||
|
||||
assertEquals(A, deserializedC.a)
|
||||
assertEquals(null, deserializedC.b)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun addAdditionalParam() {
|
||||
@ -119,22 +121,20 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun removeParameters() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters")
|
||||
val f = File(path.toURI())
|
||||
val resource = "EvolvabilityTests.removeParameters"
|
||||
val A = 1
|
||||
val B = "two"
|
||||
val C = "three"
|
||||
val D = 4
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
|
||||
// val scc = SerializationOutput(sf).serialize(CC(A, B, C, D))
|
||||
// f.writeBytes(scc.bytes)
|
||||
// println ("Path = $path")
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes)
|
||||
|
||||
data class CC(val b: String, val d: Int)
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters")
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
|
||||
|
||||
@ -146,23 +146,22 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun addAndRemoveParameters() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAndRemoveParameters")
|
||||
val f = File(path.toURI())
|
||||
val A = 1
|
||||
val B = "two"
|
||||
val C = "three"
|
||||
val D = 4
|
||||
val E = null
|
||||
|
||||
val resource = "EvolvabilityTests.addAndRemoveParameters"
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
|
||||
// val scc = SerializationOutput(sf).serialize(CC(A, B, C, D))
|
||||
// f.writeBytes(scc.bytes)
|
||||
// println ("Path = $path")
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes)
|
||||
|
||||
data class CC(val a: Int, val e: Boolean?, val d: Int)
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
|
||||
|
||||
@ -174,16 +173,12 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun addMandatoryFieldWithAltConstructor() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addMandatoryFieldWithAltConstructor")
|
||||
val f = File(path.toURI())
|
||||
val A = 1
|
||||
val resource = "EvolvabilityTests.addMandatoryFieldWithAltConstructor"
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class CC(val a: Int)
|
||||
// val scc = SerializationOutput(sf).serialize(CC(A))
|
||||
// f.writeBytes(scc.bytes)
|
||||
// println ("Path = $path")
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A)).bytes)
|
||||
|
||||
@Suppress("UNUSED")
|
||||
data class CC(val a: Int, val b: String) {
|
||||
@ -191,6 +186,8 @@ class EvolvabilityTests {
|
||||
constructor (a: Int) : this(a, "hello")
|
||||
}
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
|
||||
|
||||
@ -228,19 +225,14 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun addMandatoryFieldWithAltReorderedConstructor() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource(
|
||||
"EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor")
|
||||
val f = File(path.toURI())
|
||||
val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor"
|
||||
val A = 1
|
||||
val B = 100
|
||||
val C = "This is not a banana"
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class CC(val a: Int, val b: Int, val c: String)
|
||||
// val scc = SerializationOutput(sf).serialize(CC(A, B, C))
|
||||
// f.writeBytes(scc.bytes)
|
||||
// println ("Path = $path")
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes)
|
||||
|
||||
@Suppress("UNUSED")
|
||||
data class CC(val a: Int, val b: Int, val c: String, val d: String) {
|
||||
@ -250,6 +242,8 @@ class EvolvabilityTests {
|
||||
constructor (c: String, a: Int, b: Int) : this(a, b, c, "wibble")
|
||||
}
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
|
||||
|
||||
@ -262,20 +256,15 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun addMandatoryFieldWithAltReorderedConstructorAndRemoval() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource(
|
||||
"EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval")
|
||||
val f = File(path.toURI())
|
||||
val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval"
|
||||
val A = 1
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val B = 100
|
||||
val C = "This is not a banana"
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class CC(val a: Int, val b: Int, val c: String)
|
||||
// val scc = SerializationOutput(sf).serialize(CC(A, B, C))
|
||||
// f.writeBytes(scc.bytes)
|
||||
// println ("Path = $path")
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes)
|
||||
|
||||
// b is removed, d is added
|
||||
data class CC(val a: Int, val c: String, val d: String) {
|
||||
@ -286,6 +275,8 @@ class EvolvabilityTests {
|
||||
constructor (c: String, a: Int) : this(a, c, "wibble")
|
||||
}
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
|
||||
|
||||
@ -297,10 +288,9 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun multiVersion() {
|
||||
val sf = testDefaultFactory()
|
||||
val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.1")
|
||||
val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2")
|
||||
val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3")
|
||||
|
||||
val resource1 = "EvolvabilityTests.multiVersion.1"
|
||||
val resource2 = "EvolvabilityTests.multiVersion.2"
|
||||
val resource3 = "EvolvabilityTests.multiVersion.3"
|
||||
val a = 100
|
||||
val b = 200
|
||||
val c = 300
|
||||
@ -310,24 +300,15 @@ class EvolvabilityTests {
|
||||
//
|
||||
// Version 1:
|
||||
// data class C (val a: Int, val b: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(a, b))
|
||||
// File(path1.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path1")
|
||||
// File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b)).bytes)
|
||||
//
|
||||
// Version 2 - add param c
|
||||
// data class C (val c: Int, val b: Int, val a: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(c, b, a))
|
||||
// File(path2.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path2")
|
||||
// File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(c, b, a)).bytes)
|
||||
//
|
||||
// Version 3 - add param d
|
||||
// data class C (val b: Int, val c: Int, val d: Int, val a: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(b, c, d, a))
|
||||
// File(path3.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path3")
|
||||
// File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, a)).bytes)
|
||||
|
||||
@Suppress("UNUSED")
|
||||
data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) {
|
||||
@ -341,6 +322,10 @@ class EvolvabilityTests {
|
||||
constructor (a: Int, b: Int, c: Int, d: Int) : this(-1, c, b, a, d)
|
||||
}
|
||||
|
||||
val path1 = EvolvabilityTests::class.java.getResource(resource1)
|
||||
val path2 = EvolvabilityTests::class.java.getResource(resource2)
|
||||
val path3 = EvolvabilityTests::class.java.getResource(resource3)
|
||||
|
||||
val sb1 = File(path1.toURI()).readBytes()
|
||||
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))
|
||||
|
||||
@ -372,24 +357,21 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun changeSubType() {
|
||||
val sf = testDefaultFactory()
|
||||
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.changeSubType")
|
||||
val f = File(path.toURI())
|
||||
val resource = "EvolvabilityTests.changeSubType"
|
||||
val oa = 100
|
||||
val ia = 200
|
||||
|
||||
// Original version of the class as it was serialised
|
||||
//
|
||||
// data class Inner (val a: Int)
|
||||
// data class Outer (val a: Int, val b: Inner)
|
||||
// val scc = SerializationOutput(sf).serialize(Outer(oa, Inner (ia)))
|
||||
// f.writeBytes(scc.bytes)
|
||||
// println ("Path = $path")
|
||||
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(Outer(oa, Inner (ia))).bytes)
|
||||
|
||||
// Add a parameter to inner but keep outer unchanged
|
||||
data class Inner(val a: Int, val b: String?)
|
||||
|
||||
data class Outer(val a: Int, val b: Inner)
|
||||
|
||||
val path = EvolvabilityTests::class.java.getResource(resource)
|
||||
val f = File(path.toURI())
|
||||
val sc2 = f.readBytes()
|
||||
val outer = DeserializationInput(sf).deserialize(SerializedBytes<Outer>(sc2))
|
||||
|
||||
@ -401,9 +383,10 @@ class EvolvabilityTests {
|
||||
@Test
|
||||
fun multiVersionWithRemoval() {
|
||||
val sf = testDefaultFactory()
|
||||
val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.1")
|
||||
val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.2")
|
||||
val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.3")
|
||||
|
||||
val resource1 = "EvolvabilityTests.multiVersionWithRemoval.1"
|
||||
val resource2 = "EvolvabilityTests.multiVersionWithRemoval.2"
|
||||
val resource3 = "EvolvabilityTests.multiVersionWithRemoval.3"
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val a = 100
|
||||
@ -417,24 +400,15 @@ class EvolvabilityTests {
|
||||
//
|
||||
// Version 1:
|
||||
// data class C (val a: Int, val b: Int, val c: Int)
|
||||
// File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b, c)).bytes)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(a, b, c))
|
||||
// File(path1.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path1")
|
||||
//
|
||||
// Version 2 - add param c
|
||||
// Version 2 - remove property a, add property e
|
||||
// data class C (val b: Int, val c: Int, val d: Int, val e: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(b, c, d, e))
|
||||
// File(path2.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path2")
|
||||
// File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e)).bytes)
|
||||
//
|
||||
// Version 3 - add param d
|
||||
// data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f))
|
||||
// File(path3.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path3")
|
||||
// File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e, f)).bytes)
|
||||
|
||||
@Suppress("UNUSED")
|
||||
data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) {
|
||||
@ -451,6 +425,10 @@ class EvolvabilityTests {
|
||||
constructor (b: Int, c: Int, d: Int, e: Int, f: Int) : this(b, c, d, e, f, -1)
|
||||
}
|
||||
|
||||
val path1 = EvolvabilityTests::class.java.getResource(resource1)
|
||||
val path2 = EvolvabilityTests::class.java.getResource(resource2)
|
||||
val path3 = EvolvabilityTests::class.java.getResource(resource3)
|
||||
|
||||
val sb1 = File(path1.toURI()).readBytes()
|
||||
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))
|
||||
|
||||
|
@ -164,6 +164,8 @@ class SerializationOutputTests {
|
||||
this.register(Choice.DESCRIPTOR, Choice.Companion)
|
||||
this.register(RestrictedType.DESCRIPTOR, RestrictedType.Companion)
|
||||
this.register(ReferencedObject.DESCRIPTOR, ReferencedObject.Companion)
|
||||
this.register(TransformsSchema.DESCRIPTOR, TransformsSchema.Companion)
|
||||
this.register(TransformTypes.DESCRIPTOR, TransformTypes.Companion)
|
||||
}
|
||||
EncoderImpl(decoder)
|
||||
decoder.setByteBuffer(ByteBuffer.wrap(bytes.bytes, 8, bytes.size - 8))
|
||||
@ -430,9 +432,7 @@ class SerializationOutputTests {
|
||||
|
||||
private fun serdesThrowableWithInternalInfo(t: Throwable, factory: SerializerFactory, factory2: SerializerFactory, expectedEqual: Boolean = true): Throwable = withTestSerialization {
|
||||
val newContext = SerializationFactory.defaultFactory.defaultContext.withProperty(CommonPropertyNames.IncludeInternalInfo, true)
|
||||
|
||||
val deserializedObj = SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
|
||||
return deserializedObj
|
||||
SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user