Merge pull request #89 from corda/christians_os_merge_20171031

OpenSource -> Enterprise
This commit is contained in:
Christian Sailer 2017-11-06 12:50:17 +00:00 committed by GitHub
commit 7800b6cb4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1216 changed files with 3677 additions and 432263 deletions

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,4 +15,5 @@ import kotlin.reflect.KClass
* @see InitiatingFlow
*/
@Target(CLASS)
@MustBeDocumented
annotation class InitiatedBy(val value: KClass<out FlowLogic<*>>)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -19,4 +19,5 @@ Tutorials
tutorial-custom-notary
tutorial-tear-offs
tutorial-attachments
event-scheduling
event-scheduling
tutorial-observer-nodes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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