diff --git a/.ci/api-current.txt b/.ci/api-current.txt
index e66520b6f2..5fcde86cee 100644
--- a/.ci/api-current.txt
+++ b/.ci/api-current.txt
@@ -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
diff --git a/.ci/dependency-checker/suppressedLibraries.xml b/.ci/dependency-checker/suppressedLibraries.xml
index 1150abff87..529b911945 100644
--- a/.ci/dependency-checker/suppressedLibraries.xml
+++ b/.ci/dependency-checker/suppressedLibraries.xml
@@ -10,5 +10,22 @@
cpe:/a:apache:struts:2.0.0
-->
-
+
+
+
+ ^io\.atomix\.catalyst:catalyst-netty:.*$
+ CVE-2014-3488
+
+
+
+
+ ^commons-cli:commons-cli:.*$
+ CVE-2016-6497
+
+
+
+
+ ^commons-cli:commons-cli:.*$
+ CVE-2015-3253
+
diff --git a/.gitignore b/.gitignore
index 4a74543f4c..65727aecdf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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.
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 360c6ff7bd..e437f00cb7 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,9 +1,7 @@
-
-
-
+
@@ -18,16 +16,11 @@
-
-
-
+
-
-
-
@@ -37,9 +30,6 @@
-
-
-
@@ -49,8 +39,6 @@
-
-
@@ -91,12 +79,8 @@
-
-
-
-
@@ -107,15 +91,6 @@
-
-
-
-
-
-
-
-
-
@@ -123,18 +98,8 @@
-
-
-
-
-
-
-
-
-
-
@@ -146,16 +111,13 @@
+
+
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml
index 321d3d2d06..b4b35f6ef4 100644
--- a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml
+++ b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml
@@ -1,6 +1,7 @@
+
diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml
index ea61b6ef8d..8cbc27a7c0 100644
--- a/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml
+++ b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml
@@ -1,6 +1,7 @@
+
diff --git a/.idea/runConfigurations/Explorer___demo_nodes.xml b/.idea/runConfigurations/Explorer___demo_nodes.xml
index 42dcfcb487..fc6c82bb63 100644
--- a/.idea/runConfigurations/Explorer___demo_nodes.xml
+++ b/.idea/runConfigurations/Explorer___demo_nodes.xml
@@ -1,6 +1,7 @@
+
diff --git a/.idea/runConfigurations/Explorer___demo_nodes__simulation_.xml b/.idea/runConfigurations/Explorer___demo_nodes__simulation_.xml
index 6671745713..06db882b8e 100644
--- a/.idea/runConfigurations/Explorer___demo_nodes__simulation_.xml
+++ b/.idea/runConfigurations/Explorer___demo_nodes__simulation_.xml
@@ -1,6 +1,7 @@
+
diff --git a/build.gradle b/build.gradle
index 8b11a16368..2edbbb0cd9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,19 +22,19 @@ buildscript {
ext.asm_version = '0.5.3'
ext.artemis_version = '2.1.0'
- ext.jackson_version = '2.8.5'
- ext.jetty_version = '9.3.9.v20160517'
+ ext.jackson_version = '2.9.2'
+ ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
ext.jolokia_version = '2.0.0-M3'
- ext.assertj_version = '3.6.1'
+ ext.assertj_version = '3.8.0'
ext.slf4j_version = '1.7.25'
- ext.log4j_version = '2.7'
+ ext.log4j_version = '2.9.1'
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
ext.guava_version = constants.getProperty("guavaVersion")
ext.okhttp_version = '3.5.0'
ext.netty_version = '4.1.9.Final'
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
- ext.fileupload_version = '1.3.2'
+ ext.fileupload_version = '1.3.3'
ext.junit_version = '4.12'
ext.mockito_version = '2.10.0'
ext.jopt_simple_version = '5.0.2'
@@ -46,6 +46,8 @@ buildscript {
ext.dokka_version = '0.9.14'
ext.eddsa_version = '0.2.0'
ext.dependency_checker_version = '3.0.1'
+ ext.commons_collections_version = '4.1'
+ ext.beanutils_version = '1.9.3'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131'
@@ -196,9 +198,6 @@ if (!JavaVersion.current().java8Compatible)
repositories {
mavenCentral()
jcenter()
- maven {
- url 'https://dl.bintray.com/kotlin/exposed'
- }
}
// Required for building out the fat JAR.
diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
index c9ee7af180..d84cbd3a62 100644
--- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
@@ -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
diff --git a/client/jfx/build.gradle b/client/jfx/build.gradle
index aaf1d5fd14..0e84e78f37 100644
--- a/client/jfx/build.gradle
+++ b/client/jfx/build.gradle
@@ -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.
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
index 636603d1ab..92870b671b 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt
@@ -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 = 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 = SimpleObjectProperty(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)
+ }
})
}
diff --git a/client/jfx/src/test/kotlin/net/corda/client/jfx/model/ExchangeRateModelTest.kt b/client/jfx/src/test/kotlin/net/corda/client/jfx/model/ExchangeRateModelTest.kt
new file mode 100644
index 0000000000..6b1a1a9ae2
--- /dev/null
+++ b/client/jfx/src/test/kotlin/net/corda/client/jfx/model/ExchangeRateModelTest.kt
@@ -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, another: Amount) {
+ 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")
+ }
+}
\ No newline at end of file
diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java
index 4c0245462d..d46afb11b9 100644
--- a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java
+++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java
@@ -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> 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()));
}
diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt
index 89264f2e05..14ae217a2b 100644
--- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt
+++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt
@@ -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()
const val EVIL: Long = 666
}
@StartableByRPC
- @InitiatingFlow
- class FlowC(private val remoteParty: Party, private val data: Packet) : FlowLogic() {
+ class FlowC(@Suppress("unused") private val data: Packet) : FlowLogic() {
@Suspendable
- override fun call() {
- val session = initiateFlow(remoteParty)
- val x = session.sendAndReceive(data).unwrap { x -> x }
- logger.info("FlowC: ${x.x()}")
- }
+ override fun call() = Unit
}
- @InitiatedBy(FlowC::class)
- class RemoteFlowC(private val session: FlowSession) : FlowLogic() {
- @Suspendable
- override fun call() {
- val packet = session.receive().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
- private lateinit var bobNode: StartedNode
- 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")
+ }
}
}
\ No newline at end of file
diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt
index 9479ffe36a..e5b412b596 100644
--- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt
+++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt
@@ -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!!)
}
diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt
index 27fb5b6791..d323b38b30 100644
--- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt
+++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt
@@ -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()
diff --git a/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt b/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt
index a5f9c709a2..8ad0fb2fc0 100644
--- a/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt
@@ -15,4 +15,5 @@ import kotlin.reflect.KClass
* @see InitiatingFlow
*/
@Target(CLASS)
+@MustBeDocumented
annotation class InitiatedBy(val value: KClass>)
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt
index 2e686e8746..76dc39dba7 100644
--- a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt
@@ -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() {
- /** 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() {
+ @Suppress("KDocMissingDocumentation")
@Suspendable
@Throws(SignatureException::class,
AttachmentResolutionException::class,
TransactionResolutionException::class,
TransactionVerificationException::class)
override fun call(): SignedTransaction {
- return otherSideSession.receive().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().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
}
}
diff --git a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt
index e0fec0f7ac..3b531d3cf6 100644
--- a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt
@@ -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,
// 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 {
diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
index ce08e56881..41d0980651 100644
--- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
+++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
@@ -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)
+ fun recordTransactions(notifyVault: Boolean, txs: Iterable) {
+ 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)
+
/**
* 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) {
- recordTransactions(true, txs)
+ recordTransactions(StatesToRecord.ONLY_RELEVANT, txs)
}
/**
diff --git a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
index a02e65bfcb..80c0d842ae 100644
--- a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
+++ b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt
@@ -105,7 +105,7 @@ object NodeInfoSchemaV1 : MappedSchema(
private val persistentNodeInfos: Set = 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)
diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt
new file mode 100644
index 0000000000..aa1e9c7154
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt
@@ -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)
+
diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt
new file mode 100644
index 0000000000..c8ae7e2aec
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt
@@ -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)
diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt
index 2b5705a187..bd5a0cc2e5 100644
--- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt
+++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt
@@ -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()
}
/**
diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt
new file mode 100644
index 0000000000..9585551bd0
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt
@@ -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
+}
diff --git a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt
index b50aa66b99..70995cd8cd 100644
--- a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt
+++ b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt
@@ -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)
diff --git a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt
index 24509c3c52..361fb6e876 100644
--- a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt
+++ b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt
@@ -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()
diff --git a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt
index aa8b071299..6e835d5209 100644
--- a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt
+++ b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt
@@ -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
diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt
index 7772890a38..26878fea9c 100644
--- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt
+++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt
@@ -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) {
diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
index 0c65317b44..60b5306cb6 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
@@ -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()
diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt
index e476f64cb5..cb3fbb54f4 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt
@@ -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
diff --git a/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt b/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt
index c8f35a77a5..77ce4e0ba8 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt
@@ -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()
diff --git a/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt b/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt
index 287e7de0c9..5f35ff8de4 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt
@@ -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. */
diff --git a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt
index a9198b26e7..0b839c08e8 100644
--- a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt
+++ b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt
@@ -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)
}
}
diff --git a/core/src/test/kotlin/net/corda/core/serialization/CommandsSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/CommandsSerializationTests.kt
index 5c049c5638..836157e2c3 100644
--- a/core/src/test/kotlin/net/corda/core/serialization/CommandsSerializationTests.kt
+++ b/core/src/test/kotlin/net/corda/core/serialization/CommandsSerializationTests.kt
@@ -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`() {
diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt
index 6a3324e6dd..803fb57628 100644
--- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt
+++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt
@@ -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 {
diff --git a/core/src/test/kotlin/net/corda/core/serialization/UniquenessExceptionSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/UniquenessExceptionSerializationTest.kt
index 45faec65ce..8c431b4e20 100644
--- a/core/src/test/kotlin/net/corda/core/serialization/UniquenessExceptionSerializationTest.kt
+++ b/core/src/test/kotlin/net/corda/core/serialization/UniquenessExceptionSerializationTest.kt
@@ -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() {
diff --git a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt
index b4ef01dbbc..ec5f5c8fc1 100644
--- a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt
+++ b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt
@@ -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()
diff --git a/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt b/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt
index 299dec166e..8d7cb3e12f 100644
--- a/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt
+++ b/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt
@@ -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()
}
}
}
diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html
index a9928b3bea..25db2f8b36 100644
--- a/docs/source/_templates/layout.html
+++ b/docs/source/_templates/layout.html
@@ -8,8 +8,8 @@
API reference: Kotlin/ JavaDoc
-Discourse Forums
- Slack
-{% endblock %}
+Stack Overflow
+
+{% endblock %}
\ No newline at end of file
diff --git a/docs/source/_templates/layout_for_doc_website.html b/docs/source/_templates/layout_for_doc_website.html
index 2272ea7fb4..f0992030c9 100644
--- a/docs/source/_templates/layout_for_doc_website.html
+++ b/docs/source/_templates/layout_for_doc_website.html
@@ -6,10 +6,10 @@
API reference: Kotlin/ JavaDoc
-Discourse Forums
- Slack
+Stack Overflow
+
{{ version }}
diff --git a/docs/source/api-identity.rst b/docs/source/api-identity.rst
index fb3a18342e..3ab041d6b2 100644
--- a/docs/source/api-identity.rst
+++ b/docs/source/api-identity.rst
@@ -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.
\ No newline at end of file
+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.
\ No newline at end of file
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
index aa7af701b7..1da092f055 100644
--- a/docs/source/changelog.rst
+++ b/docs/source/changelog.rst
@@ -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.
diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst
index 6b964d60a6..74b476b0a0 100644
--- a/docs/source/corda-configuration-file.rst
+++ b/docs/source/corda-configuration-file.rst
@@ -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.
diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt
index 9690a5ab41..a4639256f3 100644
--- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt
+++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt
@@ -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)
diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt
index a797479c86..edefef85f7 100644
--- a/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt
+++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt
@@ -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()
+ ).parseAsNodeConfiguration()
}
}
diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt
index 7a5193c0b0..5e31ac39ba 100644
--- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt
+++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt
@@ -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)
diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt
index 833f4c140d..9f98082730 100644
--- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt
+++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt
@@ -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)
diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst
index f6ce762df1..fa71c4106d 100644
--- a/docs/source/getting-set-up.rst
+++ b/docs/source/getting-set-up.rst
@@ -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 ` 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 `. You may want to refer to the :doc:`API documentation ` 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 `. You'll want to refer to the :doc:`API docs `, the
+:doc:`flow cookbook ` and the `samples `_ along the way.
If you encounter any issues, please see the :doc:`troubleshooting` page, or get in touch with us on the
`forums `_ or via `slack `_.
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 75f9733f4e..a655f25c02 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -10,11 +10,15 @@ quick introduction to distributed ledgers and how Corda is different, then watch
-Want to see Corda running? Download our demonstration application `DemoBench `_ or follow our :doc:`quickstart guide `.
+Want to see Corda running? Download our demonstration application `DemoBench `_ or
+follow our :doc:`quickstart guide `.
-If you want to start coding on Corda, then familiarise yourself with the :doc:`key concepts `, then read our :doc:`Hello, World! tutorial `. 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 `, then read
+our :doc:`Hello, World! tutorial `. 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 `_, `Discourse `_, or write a question on `stackoverflow `_ .
+If you have questions or comments, then get in touch on `Slack `_ or write a question on
+`Stack Overflow `_ .
We look forward to seeing what you can do with Corda!
diff --git a/docs/source/quickstart-index.rst b/docs/source/quickstart-index.rst
index 59f6042658..02e9045ac9 100644
--- a/docs/source/quickstart-index.rst
+++ b/docs/source/quickstart-index.rst
@@ -6,6 +6,6 @@ Quickstart
getting-set-up
tutorial-cordapp
- running-the-demos
+ Sample CorDapps
building-against-master
CLI-vs-IDE
\ No newline at end of file
diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst
index 72baf8cd6f..d0a7fa14fd 100644
--- a/docs/source/release-notes.rst
+++ b/docs/source/release-notes.rst
@@ -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!
diff --git a/docs/source/running-the-demos.rst b/docs/source/running-the-demos.rst
index 11ebb794c2..e69de29bb2 100644
--- a/docs/source/running-the-demos.rst
+++ b/docs/source/running-the-demos.rst
@@ -1,437 +0,0 @@
-Running the demos
-=================
-
-.. contents::
-
-The `Corda repository `_ 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 `_.
-
-.. 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 `_ version of the demo will start three distributed notary nodes.
-* The `BFT SMaRt `_ 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 `_ (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 `_,
-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 `_ 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 <<>> http://localhost:10005/api/simmvaluationdemo/<<>>/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/<<>>/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/<<>>/trades/<<>>
-
-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 <<>>
- http://localhost:10005/api/simmvaluationdemo/<<>>/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/<<>>/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
-
-
-
-
-
diff --git a/docs/source/tutorial-observer-nodes.rst b/docs/source/tutorial-observer-nodes.rst
new file mode 100644
index 0000000000..ecb5f04089
--- /dev/null
+++ b/docs/source/tutorial-observer-nodes.rst
@@ -0,0 +1,42 @@
+.. highlight:: kotlin
+.. raw:: html
+
+
+
+
+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.
\ No newline at end of file
diff --git a/docs/source/tutorials-index.rst b/docs/source/tutorials-index.rst
index f14be4385f..d2b3dfce9d 100644
--- a/docs/source/tutorials-index.rst
+++ b/docs/source/tutorials-index.rst
@@ -19,4 +19,5 @@ Tutorials
tutorial-custom-notary
tutorial-tear-offs
tutorial-attachments
- event-scheduling
\ No newline at end of file
+ event-scheduling
+ tutorial-observer-nodes
\ No newline at end of file
diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
index 2d5093a583..9d18c2d3a5 100644
--- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
+++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
@@ -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()) {
diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
index d1d6751aa1..1f9e441faf 100644
--- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
+++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
@@ -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()
}
}
diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt
index df14ca6843..eda85bb636 100644
--- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt
+++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt
@@ -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>
@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().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(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 { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 1) }
+ Unit
}
/**
* Try exiting with a reference mis-match.
*/
@Test
- fun generateInvalidReferenceExit() {
- initialiseTestSerialization()
+ fun generateInvalidReferenceExit() = withTestSerialization {
assertFailsWith { 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 { makeExit(miniCorpServices, 1000.DOLLARS, MEGA_CORP, 1) }
+ Unit
}
/**
* Try exiting for an owner with no states
*/
@Test
- fun generateOwnerWithNoStatesExit() {
- initialiseTestSerialization()
+ fun generateOwnerWithNoStatesExit() = withTestSerialization {
assertFailsWith { makeExit(miniCorpServices, 100.POUNDS, CHARLIE, 1) }
+ Unit
}
/**
* Try exiting when vault is empty
*/
@Test
- fun generateExitWithEmptyVault() {
- initialiseTestSerialization()
+ fun generateExitWithEmptyVault() = withTestSerialization {
assertFailsWith {
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(
diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt
index c2d683a74b..e10a8d9674 100644
--- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt
+++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt
@@ -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().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().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().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
@@ -228,12 +219,12 @@ class ObligationTests {
ptx.addInputState(tx.outRef>(0))
Obligation().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, Amount> = mapOf(Pair(Pair(ALICE, BOB), Amount(100000000, GBP)))
val expected: Map = mapOf(Pair(ALICE, -100000000L), Pair(BOB, 100000000L))
val actual = sumAmountsDue(simple)
diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ApiScanner.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ApiScanner.java
index 0861605258..75f238891c 100644
--- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ApiScanner.java
+++ b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ApiScanner.java
@@ -32,11 +32,11 @@ public class ApiScanner implements Plugin {
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.
diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt
index b0e09ad2da..c28c8b645e 100644
--- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt
+++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt
@@ -18,10 +18,12 @@ class Cordformation : Plugin {
* @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)
diff --git a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt b/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt
index 83462c2deb..04b01654f3 100644
--- a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt
+++ b/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt
@@ -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 -> {
diff --git a/node-api/build.gradle b/node-api/build.gradle
index a9384bd4ab..76abc37bea 100644
--- a/node-api/build.gradle
+++ b/node-api/build.gradle
@@ -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}"
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt
index 45f44c899a..672c11aeed 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt
@@ -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
- }
- }
}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt
new file mode 100644
index 0000000000..78ef9dc52a
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt
@@ -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)
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt
new file mode 100644
index 0000000000..5b489c5d84
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt
@@ -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 {
+ 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)
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt
index cfb505ab6c..bb38933ed0 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt
@@ -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 {
- 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) : DescribedType {
companion object : DescribedTypeConstructor {
- 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 {
- 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, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType {
companion object : DescribedTypeConstructor {
- 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, override val descriptor: Descriptor, val fields: List) : TypeNotation() {
companion object : DescribedTypeConstructor {
- 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) : TypeNotation() {
companion object : DescribedTypeConstructor {
- 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 {
- 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 {
- val DESCRIPTOR = DescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor
+ val DESCRIPTOR = AMQPDescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor
fun get(obj: Any): ReferencedObject {
val describedType = obj as DescribedType
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt
index 87a19376c9..2c0c4ece9f 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt
@@ -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()
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt
index da8ae1afc6..5075593ace 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt
@@ -34,6 +34,8 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
private val serializersByType = ConcurrentHashMap>()
private val serializersByDescriptor = ConcurrentHashMap>()
private val customSerializers = CopyOnWriteArrayList>()
+ val transformsCache = ConcurrentHashMap>>()
+
open val classCarpenter = ClassCarpenter(cl, whitelist)
val classloader: ClassLoader
get() = classCarpenter.classloader
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt
new file mode 100644
index 0000000000..b785d0e8c6
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt
@@ -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,
+ val enum: TransformTypes,
+ val getAnnotations: (Annotation) -> List)
+
+/**
+ * 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).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)
+)
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt
new file mode 100644
index 0000000000..5bb6fc05d5
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt
@@ -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 {
+ 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
+ }
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt
new file mode 100644
index 0000000000..2bde766c2c
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt
@@ -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 {
+ 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 {
+ val typeName = "UnknownTransform"
+
+ override fun newInstance(obj: Any?) = UnknownTransform()
+
+ override fun getTypeClass(): Class<*> = UnknownTransform::class.java
+ }
+
+ override fun getDescribed(): Any = emptyList()
+ override fun params() = ""
+
+ override val name: String get() = typeName
+}
+
+class UnknownTestTransform(val a: Int, val b: Int, val c: Int) : Transform() {
+ companion object : DescribedTypeConstructor {
+ 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 {
+ /**
+ * 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 {
+ /**
+ * 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>>) : DescribedType {
+ companion object : DescribedTypeConstructor {
+ 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>>()
+
+ schema.types.forEach { type ->
+ sf.transformsCache.computeIfAbsent(type.name) {
+ val transforms = EnumMap>(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>>()
+ 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::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")
+ types.forEach { type ->
+ val indent = Indent(indent)
+ sb.appendln("$indent")
+ type.value.forEach { transform ->
+ val indent = Indent(indent)
+ sb.appendln("$indent")
+ transform.value.forEach {
+ val indent = Indent(indent)
+ sb.appendln("$indent")
+ }
+ sb.appendln("$indent")
+ }
+ sb.appendln("$indent")
+ }
+ sb.appendln("$indent")
+
+ return sb.toString()
+ }
+}
+
+private fun String.esc() = "\"$this\""
diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java
index eeb932ffa1..26ef012df3 100644
--- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java
+++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java
@@ -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
diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java
index 7f18f3cac7..c3230be211 100644
--- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java
+++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java
@@ -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);
}
diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java
index 34ca1abaa6..1743c645b4 100644
--- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java
+++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java
@@ -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));
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt
index 3e72b1d0d4..02eee8cd84 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt
@@ -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 {
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt
index b2cd36ff1f..a6ab0c6ecc 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt
@@ -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() {
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt
index d6cb313f88..07593368a7 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt
@@ -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
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt
index d9d0f58e0c..c59d26b72d 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt
@@ -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().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())
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt
index 4e9f598eab..5cc64a5b64 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt
@@ -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().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())
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/PrivateKeySerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/PrivateKeySerializationTest.kt
index bf9cd8f2e5..adc8097a97 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/PrivateKeySerializationTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/PrivateKeySerializationTest.kt
@@ -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() }
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt
index 64b2040642..6fc74fba55 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt
@@ -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
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt
index 210a0cd800..57cee0e7e5 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt
@@ -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().javaClass
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
+
@Test
fun `check set can be serialized as root of serialization graph`() {
assertEqualAfterRoundTripSerialization(emptySet())
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt
index 4f9b7b6872..43fe82a6ee 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt
@@ -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(val obj: SerializedBytes, val schema: Schema)
+data class BytesAndSchemas(
+ val obj: SerializedBytes,
+ 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 SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchema {
+fun SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchemas {
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()
}
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt
new file mode 100644
index 0000000000..6626f558b9
--- /dev/null
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt
@@ -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(sb1)).envelope
+
+ assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name))
+ assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown))
+ }
+}
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt
index 9c4a949384..69a5316a5e 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt
@@ -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://////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(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(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(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(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(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(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(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(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(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(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(sb1))
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt
index 31136f0723..f3496a9f74 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt
@@ -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
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform
new file mode 100644
index 0000000000..2916621d54
Binary files /dev/null and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory
index 8e37059133..3eb318bb7e 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters
index 3943c84020..52f7551505 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor
index a7e207ac75..f6cb7d74d4 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor
index 87506ae203..584aef76d7 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval
index 922eecc335..8a093478bc 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType
index 96e11c7f09..b323a3acb3 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1
index 2cca4802ac..a7da3eeda3 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2
index 76818dc697..b00abdaeab 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2 differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3
index 1e36577ca9..40ea9d91f6 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1
index 9496b5632a..69a5365b76 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2
index 44cd5fb07f..a3fec70686 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3
index c1dcb85ab8..c00a282136 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters
index 65d11e24e3..37c419f076 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType
index 6d81269d3e..cc726a13fd 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType differ
diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType
index b96b0ce60e..dc9fae7152 100644
Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType differ
diff --git a/node/build.gradle b/node/build.gradle
index 2a7f24aa52..169593924b 100644
--- a/node/build.gradle
+++ b/node/build.gradle
@@ -95,6 +95,9 @@ dependencies {
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
// Artemis: for reliable p2p message queues.
+ // 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-server:${artemis_version}"
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
runtime ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt
index 211f0e1f15..6cddc95bed 100644
--- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt
@@ -102,7 +102,7 @@ class NodePerformanceTests {
@Test
fun `self pay rate`() {
- driver(startNodesInProcess = true) {
+ driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) {
val a = startNotaryNode(
DUMMY_NOTARY.name,
rpcUsers = listOf(User("A", "A", setOf(startFlowPermission(), startFlowPermission())))
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt
index 9d4abec634..2065c7d350 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt
@@ -20,22 +20,24 @@ import net.corda.core.utilities.seconds
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.User
-import net.corda.testing.DUMMY_BANK_A
-import net.corda.testing.DUMMY_NOTARY
-import net.corda.testing.TestDependencyInjectionBase
+import net.corda.testing.*
import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
-import net.corda.testing.eventually
import net.corda.testing.node.MockServices
import org.junit.Assert.assertEquals
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.net.URLClassLoader
import java.nio.file.Files
import kotlin.test.assertFailsWith
-class AttachmentLoadingTests : TestDependencyInjectionBase() {
+class AttachmentLoadingTests {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
+
private class Services : MockServices() {
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
private val cordapp get() = provider.cordapps.first()
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt
index 3ba78ff7bd..745b763e9e 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt
@@ -8,53 +8,53 @@ import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map
-import net.corda.core.internal.concurrent.transpose
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.node.services.transactions.RaftValidatingNotaryService
-import net.corda.testing.*
+import net.corda.testing.DUMMY_BANK_A
+import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract
-import net.corda.testing.node.NodeBasedTest
+import net.corda.testing.driver.NodeHandle
+import net.corda.testing.driver.driver
+import net.corda.testing.dummyCommand
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
-class RaftNotaryServiceTests : NodeBasedTest(listOf("net.corda.testing.contracts")) {
+class RaftNotaryServiceTests {
private val notaryName = CordaX500Name(RaftValidatingNotaryService.id, "RAFT Notary Service", "London", "GB")
@Test
fun `detect double spend`() {
- val (bankA) = listOf(
- startNode(DUMMY_BANK_A.name),
- startNotaryCluster(notaryName, 3).map { it.first() }
- ).transpose().getOrThrow()
+ driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts")) {
+ val (notaryParty) = startNotaryCluster(notaryName, 3).getOrThrow()
+ val bankA = startNode(providedName = DUMMY_BANK_A.name).map { (it as NodeHandle.InProcess).node }.getOrThrow()
- val notaryParty = bankA.services.networkMapCache.getNotary(notaryName)!!
+ val inputState = issueState(bankA, notaryParty)
- val inputState = issueState(bankA, notaryParty)
+ val firstTxBuilder = TransactionBuilder(notaryParty)
+ .addInputState(inputState)
+ .addCommand(dummyCommand(bankA.services.myInfo.chooseIdentity().owningKey))
+ val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder)
- val firstTxBuilder = TransactionBuilder(notaryParty)
- .addInputState(inputState)
- .addCommand(dummyCommand(bankA.services.myInfo.chooseIdentity().owningKey))
- val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder)
+ val firstSpend = bankA.services.startFlow(NotaryFlow.Client(firstSpendTx))
+ firstSpend.resultFuture.getOrThrow()
- val firstSpend = bankA.services.startFlow(NotaryFlow.Client(firstSpendTx))
- firstSpend.resultFuture.getOrThrow()
+ val secondSpendBuilder = TransactionBuilder(notaryParty).withItems(inputState).run {
+ val dummyState = DummyContract.SingleOwnerState(0, bankA.info.chooseIdentity())
+ addOutputState(dummyState, DummyContract.PROGRAM_ID)
+ addCommand(dummyCommand(bankA.services.myInfo.chooseIdentity().owningKey))
+ this
+ }
+ val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder)
+ val secondSpend = bankA.services.startFlow(NotaryFlow.Client(secondSpendTx))
- val secondSpendBuilder = TransactionBuilder(notaryParty).withItems(inputState).run {
- val dummyState = DummyContract.SingleOwnerState(0, bankA.info.chooseIdentity())
- addOutputState(dummyState, DummyContract.PROGRAM_ID)
- addCommand(dummyCommand(bankA.services.myInfo.chooseIdentity().owningKey))
- this
+ val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() }
+ val error = ex.error as NotaryError.Conflict
+ assertEquals(error.txId, secondSpendTx.id)
}
- val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder)
- val secondSpend = bankA.services.startFlow(NotaryFlow.Client(secondSpendTx))
-
- val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() }
- val error = ex.error as NotaryError.Conflict
- assertEquals(error.txId, secondSpendTx.id)
}
private fun issueState(node: StartedNode<*>, notary: Party): StateAndRef<*> {
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt
index d307a02705..8658de9091 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt
@@ -13,14 +13,12 @@ import net.corda.testing.ALICE
import net.corda.testing.ALICE_KEY
import net.corda.testing.DEV_TRUST_ROOT
import net.corda.testing.getTestPartyAndCertificate
+import net.corda.testing.internal.NodeBasedTest
import net.corda.testing.node.MockKeyManagementService
-import net.corda.testing.node.NodeBasedTest
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.contentOf
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TemporaryFolder
import rx.observers.TestSubscriber
import rx.schedulers.TestScheduler
import java.nio.file.Path
@@ -29,42 +27,36 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
class NodeInfoWatcherTest : NodeBasedTest() {
-
- @Rule
- @JvmField
- var folder = TemporaryFolder()
-
- lateinit var keyManagementService: KeyManagementService
- lateinit var nodeInfoPath: Path
- val scheduler = TestScheduler()
- val testSubscriber = TestSubscriber()
-
- // Object under test
- lateinit var nodeInfoWatcher: NodeInfoWatcher
-
companion object {
val nodeInfo = NodeInfo(listOf(), listOf(getTestPartyAndCertificate(ALICE)), 0, 0)
}
+ private lateinit var keyManagementService: KeyManagementService
+ private lateinit var nodeInfoPath: Path
+ private val scheduler = TestScheduler()
+ private val testSubscriber = TestSubscriber()
+
+ // Object under test
+ private lateinit var nodeInfoWatcher: NodeInfoWatcher
+
@Before
fun start() {
val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT)
keyManagementService = MockKeyManagementService(identityService, ALICE_KEY)
- nodeInfoWatcher = NodeInfoWatcher(folder.root.toPath(), scheduler = scheduler)
- nodeInfoPath = folder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY
+ nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler = scheduler)
+ nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY
}
@Test
fun `save a NodeInfo`() {
- assertEquals(0,
- folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size)
- NodeInfoWatcher.saveToFile(folder.root.toPath(), nodeInfo, keyManagementService)
+ assertEquals(0, tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size)
+ NodeInfoWatcher.saveToFile(tempFolder.root.toPath(), nodeInfo, keyManagementService)
- val nodeInfoFiles = folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }
+ val nodeInfoFiles = tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }
assertEquals(1, nodeInfoFiles.size)
val fileName = nodeInfoFiles.first()
assertTrue(fileName.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX))
- val file = (folder.root.path / fileName).toFile()
+ val file = (tempFolder.root.path / fileName).toFile()
// Just check that something is written, another tests verifies that the written value can be read back.
assertThat(contentOf(file)).isNotEmpty()
}
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt
index 00671b35a9..30ac56d490 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt
@@ -1,41 +1,28 @@
package net.corda.node.services.network
-import co.paralleluniverse.fibers.Suspendable
-import net.corda.core.flows.FlowLogic
-import net.corda.core.flows.FlowSession
-import net.corda.core.flows.InitiatedBy
-import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
-import net.corda.core.utilities.*
+import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
-import net.corda.testing.*
-import net.corda.testing.node.NodeBasedTest
-import org.assertj.core.api.Assertions.assertThat
+import net.corda.testing.ALICE
+import net.corda.testing.BOB
+import net.corda.testing.DUMMY_NOTARY
+import net.corda.testing.chooseIdentity
+import net.corda.testing.internal.NodeBasedTest
import org.junit.Before
import org.junit.Test
-import java.time.Duration
import kotlin.test.assertEquals
-import kotlin.test.assertFails
-import kotlin.test.assertTrue
-
-private const val BRIDGE_RETRY_MS: Long = 100
class PersistentNetworkMapCacheTest : NodeBasedTest() {
private val partiesList = listOf(DUMMY_NOTARY, ALICE, BOB)
- private val addressesMap: HashMap = HashMap()
+ private val addressesMap = HashMap()
private val infos: MutableSet = HashSet()
- companion object {
- val logger = loggerFor()
- }
-
@Before
fun start() {
val nodes = startNodesWithPort(partiesList)
- nodes.forEach { it.internals.nodeReadyFuture.get() } // Need to wait for network map registration, as these tests are ran without waiting.
nodes.forEach {
infos.add(it.info)
addressesMap[it.info.chooseIdentity().name] = it.info.addresses[0]
@@ -44,8 +31,8 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
}
@Test
- fun `get nodes by owning key and by name, no network map service`() {
- val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0]
+ fun `get nodes by owning key and by name`() {
+ val alice = startNodesWithPort(listOf(ALICE))[0]
val netCache = alice.services.networkMapCache
alice.database.transaction {
val res = netCache.getNodeByLegalIdentity(alice.info.chooseIdentity())
@@ -56,8 +43,8 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
}
@Test
- fun `get nodes by address no network map service`() {
- val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0]
+ fun `get nodes by address`() {
+ val alice = startNodesWithPort(listOf(ALICE))[0]
val netCache = alice.services.networkMapCache
alice.database.transaction {
val res = netCache.getNodeByAddress(alice.info.addresses[0])
@@ -66,150 +53,20 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
}
@Test
- fun `restart node with DB map cache and no network map`() {
- val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0]
+ fun `restart node with DB map cache`() {
+ val alice = startNodesWithPort(listOf(ALICE))[0]
val partyNodes = alice.services.networkMapCache.allNodes
- assertEquals(NullNetworkMapService, alice.inNodeNetworkMapService)
assertEquals(infos.size, partyNodes.size)
assertEquals(infos.flatMap { it.legalIdentities }.toSet(), partyNodes.flatMap { it.legalIdentities }.toSet())
}
- @Test
- fun `start 2 nodes without pointing at NetworkMapService and communicate with each other`() {
- val parties = partiesList.subList(1, partiesList.size)
- val nodes = startNodesWithPort(parties, noNetworkMap = true)
- assertTrue(nodes.all { it.inNodeNetworkMapService == NullNetworkMapService })
- nodes.forEach {
- val partyNodes = it.services.networkMapCache.allNodes
- assertEquals(infos.size, partyNodes.size)
- assertEquals(infos.flatMap { it.legalIdentities }.toSet(), partyNodes.flatMap { it.legalIdentities }.toSet())
- }
- checkConnectivity(nodes)
- }
-
- @Test
- fun `start 2 nodes pointing at NetworkMapService but don't start network map node`() {
- val parties = partiesList.subList(1, partiesList.size)
- val nodes = startNodesWithPort(parties, noNetworkMap = false)
- assertTrue(nodes.all { it.inNodeNetworkMapService == NullNetworkMapService })
- nodes.forEach {
- val partyNodes = it.services.networkMapCache.allNodes
- assertEquals(infos.size, partyNodes.size)
- assertEquals(infos.flatMap { it.legalIdentities }.toSet(), partyNodes.flatMap { it.legalIdentities }.toSet())
- }
- checkConnectivity(nodes)
- }
-
- @Test
- fun `start node and network map communicate`() {
- val parties = partiesList.subList(0, 2)
- val nodes = startNodesWithPort(parties, noNetworkMap = false)
- checkConnectivity(nodes)
- }
-
- @Test
- fun `start node without networkMapService and no database - fail`() {
- assertFails { startNode(CHARLIE.name, noNetworkMap = true).getOrThrow(2.seconds) }
- }
-
- @Test
- fun `new node joins network without network map started`() {
-
- fun customNodesStart(parties: List): List> =
- startNodesWithPort(parties, noNetworkMap = false, customRetryIntervalMs = BRIDGE_RETRY_MS)
-
- val parties = partiesList.subList(1, partiesList.size)
- // Start 2 nodes pointing at network map, but don't start network map service.
- val otherNodes = customNodesStart(parties)
- otherNodes.forEach { node ->
- assertTrue(infos.any { it.legalIdentitiesAndCerts.toSet() == node.info.legalIdentitiesAndCerts.toSet() })
- }
- // Start node that is not in databases of other nodes. Point to NMS. Which has't started yet.
- val charlie = customNodesStart(listOf(CHARLIE)).single()
- otherNodes.forEach {
- assertThat(it.services.networkMapCache.allNodes).doesNotContain(charlie.info)
- }
- // Start Network Map and see that charlie node appears in caches.
- val nms = customNodesStart(listOf(DUMMY_NOTARY)).single()
- nms.internals.startupComplete.get()
- assertTrue(nms.inNodeNetworkMapService != NullNetworkMapService)
- assertTrue(infos.any { it.legalIdentities.toSet() == nms.info.legalIdentities.toSet() })
- otherNodes.forEach {
- assertTrue(nms.info.chooseIdentity() in it.services.networkMapCache.allNodes.map { it.chooseIdentity() })
- }
- charlie.internals.nodeReadyFuture.get() // Finish registration.
-
- val allTheStartedNodesPopulation = otherNodes.plus(charlie).plus(nms)
-
- // This is prediction of the longest time it will take to get the cluster into a stable state such that further
- // testing can be performed upon it
- val maxInstabilityInterval = BRIDGE_RETRY_MS * allTheStartedNodesPopulation.size * 30
- logger.info("Instability interval is set to: $maxInstabilityInterval ms")
-
- // TODO: Re-visit this sort of re-try for stable cluster once network map redesign is finished.
- eventually(Duration.ofMillis(maxInstabilityInterval)) {
- logger.info("Checking connectivity")
- checkConnectivity(listOf(otherNodes[0], nms)) // Checks connectivity from A to NMS.
- logger.info("Loading caches")
- val cacheA = otherNodes[0].services.networkMapCache.allNodes
- val cacheB = otherNodes[1].services.networkMapCache.allNodes
- val cacheC = charlie.services.networkMapCache.allNodes
- logger.info("Performing verification")
- assertEquals(4, cacheC.size) // Charlie fetched data from NetworkMap
- assertThat(cacheB).contains(charlie.info)
- assertEquals(cacheA.toSet(), cacheB.toSet())
- assertEquals(cacheA.toSet(), cacheC.toSet())
- }
- }
-
// HELPERS
// Helper function to restart nodes with the same host and port.
- private fun startNodesWithPort(nodesToStart: List, noNetworkMap: Boolean = false, customRetryIntervalMs: Long? = null): List> {
+ private fun startNodesWithPort(nodesToStart: List, customRetryIntervalMs: Long? = null): List> {
return nodesToStart.map { party ->
val configOverrides = (addressesMap[party.name]?.let { mapOf("p2pAddress" to it.toString()) } ?: emptyMap()) +
(customRetryIntervalMs?.let { mapOf("activeMQServer.bridge.retryIntervalMs" to it.toString()) } ?: emptyMap())
- if (party == DUMMY_NOTARY) {
- startNetworkMapNode(party.name, configOverrides = configOverrides)
- } else {
- startNode(party.name,
- configOverrides = configOverrides,
- noNetworkMap = noNetworkMap,
- waitForConnection = false).getOrThrow()
- }
- }
- }
-
- // Check that nodes are functional, communicate each with each.
- private fun checkConnectivity(nodes: List>) {
- nodes.forEach { node1 ->
- nodes.forEach { node2 ->
- if (!(node1 === node2)) { // Do not check connectivity to itself
- node2.internals.registerInitiatedFlow(SendBackFlow::class.java)
- val resultFuture = node1.services.startFlow(SendFlow(node2.info.chooseIdentity())).resultFuture
- assertThat(resultFuture.getOrThrow()).isEqualTo("Hello!")
- }
- }
- }
- }
-
- @InitiatingFlow
- private class SendFlow(val otherParty: Party) : FlowLogic() {
- @Suspendable
- override fun call(): String {
- logger.info("SEND FLOW to $otherParty")
- logger.info("Party key ${otherParty.owningKey.toBase58String()}")
- val session = initiateFlow(otherParty)
- return session.sendAndReceive("Hi!").unwrap { it }
- }
- }
-
- @InitiatedBy(SendFlow::class)
- private class SendBackFlow(val otherSideSession: FlowSession) : FlowLogic() {
- @Suspendable
- override fun call() {
- logger.info("SEND BACK FLOW to ${otherSideSession.counterparty}")
- logger.info("Party key ${otherSideSession.counterparty.owningKey.toBase58String()}")
- otherSideSession.send("Hello!")
+ startNode(party.name, configOverrides = configOverrides)
}
}
}
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt
index a32c1eae59..533a5df56a 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt
@@ -5,22 +5,20 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.Party
-import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.chooseIdentity
-import net.corda.testing.node.NodeBasedTest
+import net.corda.testing.internal.NodeBasedTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class FlowVersioningTest : NodeBasedTest() {
@Test
fun `getFlowContext returns the platform version for core flows`() {
- val (alice, bob) = listOf(
- startNode(ALICE.name, platformVersion = 2),
- startNode(BOB.name, platformVersion = 3)).transpose().getOrThrow()
+ val alice = startNode(ALICE.name, platformVersion = 2)
+ val bob = startNode(BOB.name, platformVersion = 3)
bob.internals.installCoreFlow(PretendInitiatingCoreFlow::class, ::PretendInitiatedCoreFlow)
val (alicePlatformVersionAccordingToBob, bobPlatformVersionAccordingToAlice) = alice.services.startFlow(
PretendInitiatingCoreFlow(bob.info.chooseIdentity())).resultFuture.getOrThrow()
diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt
index d6aaeb10a7..bc1817035d 100644
--- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt
@@ -28,8 +28,8 @@ import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.chooseIdentity
import net.corda.testing.configureTestSSL
+import net.corda.testing.internal.NodeBasedTest
import net.corda.testing.messaging.SimpleMQClient
-import net.corda.testing.node.NodeBasedTest
import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.apache.activemq.artemis.api.core.SimpleString
@@ -52,7 +52,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
@Before
fun start() {
- alice = startNode(ALICE.name, rpcUsers = extraRPCUsers + rpcUser).getOrThrow()
+ alice = startNode(ALICE.name, rpcUsers = extraRPCUsers + rpcUser)
attacker = createAttacker()
startAttacker(attacker)
}
@@ -87,7 +87,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
@Test
fun `create queue for peer which has not been communicated with`() {
- val bob = startNode(BOB.name).getOrThrow()
+ val bob = startNode(BOB.name)
assertAllQueueCreationAttacksFail("$PEERS_PREFIX${bob.info.chooseIdentity().owningKey.toBase58String()}")
}
@@ -219,7 +219,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
}
private fun startBobAndCommunicateWithAlice(): Party {
- val bob = startNode(BOB.name).getOrThrow()
+ val bob = startNode(BOB.name)
bob.internals.registerInitiatedFlow(ReceiveFlow::class.java)
val bobParty = bob.info.chooseIdentity()
// Perform a protocol exchange to force the peer queue to be created
diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt
index 5503d5ca19..c43185bdeb 100644
--- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt
@@ -3,10 +3,8 @@ package net.corda.services.messaging
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
-import net.corda.core.internal.concurrent.transpose
-import net.corda.core.internal.elapsedTime
+import net.corda.core.internal.concurrent.map
import net.corda.core.internal.randomOrNull
-import net.corda.core.internal.times
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.serialization.CordaSerializable
@@ -14,114 +12,122 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
+import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.node.services.messaging.*
import net.corda.node.services.transactions.RaftValidatingNotaryService
-import net.corda.testing.*
-import net.corda.testing.node.NodeBasedTest
+import net.corda.testing.ALICE
+import net.corda.testing.chooseIdentity
+import net.corda.testing.driver.DriverDSLExposedInterface
+import net.corda.testing.driver.NodeHandle
+import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat
-import org.junit.Ignore
import org.junit.Test
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
-class P2PMessagingTest : NodeBasedTest() {
+class P2PMessagingTest {
private companion object {
val DISTRIBUTED_SERVICE_NAME = CordaX500Name(RaftValidatingNotaryService.id, "DistributedService", "London", "GB")
}
- @Test
- fun `network map will work after restart`() {
- val identities = listOf(DUMMY_BANK_A, DUMMY_BANK_B, DUMMY_NOTARY)
- fun startNodes() = identities.map { startNode(it.name) }.transpose()
-
- val startUpDuration = elapsedTime { startNodes().getOrThrow() }
- // Start the network map a second time - this will restore message queues from the journal.
- // This will hang and fail prior the fix. https://github.com/corda/corda/issues/37
- clearAllNodeInfoDb() // Clear network map data from nodes databases.
- stopAllNodes()
- startNodes().getOrThrow(timeout = startUpDuration * 3)
- }
-
- @Ignore
@Test
fun `communicating with a distributed service which we're part of`() {
- val distributedService = startNotaryCluster(DISTRIBUTED_SERVICE_NAME, 2).getOrThrow()
- assertAllNodesAreUsed(distributedService, DISTRIBUTED_SERVICE_NAME, distributedService[0])
+ driver(startNodesInProcess = true) {
+ val distributedService = startDistributedService()
+ assertAllNodesAreUsed(distributedService, DISTRIBUTED_SERVICE_NAME, distributedService[0])
+ }
}
@Test
fun `distributed service requests are retried if one of the nodes in the cluster goes down without sending a response`() {
- val distributedServiceNodes = startNotaryCluster(DISTRIBUTED_SERVICE_NAME, 2).getOrThrow()
- val alice = startNode(ALICE.name, configOverrides = mapOf("messageRedeliveryDelaySeconds" to 1)).getOrThrow()
- val serviceAddress = alice.services.networkMapCache.run {
- val notaryParty = notaryIdentities.randomOrNull()!!
- alice.network.getAddressOfParty(getPartyInfo(notaryParty)!!)
+ driver(startNodesInProcess = true) {
+ val distributedServiceNodes = startDistributedService()
+ val alice = startAlice()
+ val serviceAddress = alice.services.networkMapCache.run {
+ val notaryParty = notaryIdentities.randomOrNull()!!
+ alice.network.getAddressOfParty(getPartyInfo(notaryParty)!!)
+ }
+
+ val dummyTopic = "dummy.topic"
+ val responseMessage = "response"
+
+ val crashingNodes = simulateCrashingNodes(distributedServiceNodes, dummyTopic, responseMessage)
+
+ // Send a single request with retry
+ val responseFuture = with(alice.network) {
+ val request = TestRequest(replyTo = myAddress)
+ val responseFuture = onNext(dummyTopic, request.sessionID)
+ val msg = createMessage(TopicSession(dummyTopic), data = request.serialize().bytes)
+ send(msg, serviceAddress, retryId = request.sessionID)
+ responseFuture
+ }
+ crashingNodes.firstRequestReceived.await(5, TimeUnit.SECONDS)
+ // The request wasn't successful.
+ assertThat(responseFuture.isDone).isFalse()
+ crashingNodes.ignoreRequests = false
+
+ // The retry should be successful.
+ val response = responseFuture.getOrThrow(10.seconds)
+ assertThat(response).isEqualTo(responseMessage)
}
-
- val dummyTopic = "dummy.topic"
- val responseMessage = "response"
-
- val crashingNodes = simulateCrashingNodes(distributedServiceNodes, dummyTopic, responseMessage)
-
- // Send a single request with retry
- val responseFuture = with(alice.network) {
- val request = TestRequest(replyTo = myAddress)
- val responseFuture = onNext(dummyTopic, request.sessionID)
- val msg = createMessage(TopicSession(dummyTopic), data = request.serialize().bytes)
- send(msg, serviceAddress, retryId = request.sessionID)
- responseFuture
- }
- crashingNodes.firstRequestReceived.await(5, TimeUnit.SECONDS)
- // The request wasn't successful.
- assertThat(responseFuture.isDone).isFalse()
- crashingNodes.ignoreRequests = false
-
- // The retry should be successful.
- val response = responseFuture.getOrThrow(10.seconds)
- assertThat(response).isEqualTo(responseMessage)
}
@Test
fun `distributed service request retries are persisted across client node restarts`() {
- val distributedServiceNodes = startNotaryCluster(DISTRIBUTED_SERVICE_NAME, 2).getOrThrow()
- val alice = startNode(ALICE.name, configOverrides = mapOf("messageRedeliveryDelaySeconds" to 1)).getOrThrow()
- val serviceAddress = alice.services.networkMapCache.run {
- val notaryParty = notaryIdentities.randomOrNull()!!
- alice.network.getAddressOfParty(getPartyInfo(notaryParty)!!)
+ driver(startNodesInProcess = true) {
+ val distributedServiceNodes = startDistributedService()
+ val alice = startAlice()
+ val serviceAddress = alice.services.networkMapCache.run {
+ val notaryParty = notaryIdentities.randomOrNull()!!
+ alice.network.getAddressOfParty(getPartyInfo(notaryParty)!!)
+ }
+
+ val dummyTopic = "dummy.topic"
+ val responseMessage = "response"
+
+ val crashingNodes = simulateCrashingNodes(distributedServiceNodes, dummyTopic, responseMessage)
+
+ val sessionId = random63BitValue()
+
+ // Send a single request with retry
+ with(alice.network) {
+ val request = TestRequest(sessionId, myAddress)
+ val msg = createMessage(TopicSession(dummyTopic), data = request.serialize().bytes)
+ send(msg, serviceAddress, retryId = request.sessionID)
+ }
+
+ // Wait until the first request is received
+ crashingNodes.firstRequestReceived.await(5, TimeUnit.SECONDS)
+ // Stop alice's node after we ensured that the first request was delivered and ignored.
+ alice.dispose()
+ val numberOfRequestsReceived = crashingNodes.requestsReceived.get()
+ assertThat(numberOfRequestsReceived).isGreaterThanOrEqualTo(1)
+
+ crashingNodes.ignoreRequests = false
+
+ // Restart the node and expect a response
+ val aliceRestarted = startAlice()
+ val response = aliceRestarted.network.onNext(dummyTopic, sessionId).getOrThrow(5.seconds)
+
+ assertThat(crashingNodes.requestsReceived.get()).isGreaterThan(numberOfRequestsReceived)
+ assertThat(response).isEqualTo(responseMessage)
}
+ }
- val dummyTopic = "dummy.topic"
- val responseMessage = "response"
+ private fun DriverDSLExposedInterface.startDistributedService(): List> {
+ return startNotaryCluster(DISTRIBUTED_SERVICE_NAME, 2)
+ .getOrThrow()
+ .second
+ .map { (it as NodeHandle.InProcess).node }
+ }
- val crashingNodes = simulateCrashingNodes(distributedServiceNodes, dummyTopic, responseMessage)
-
- val sessionId = random63BitValue()
-
- // Send a single request with retry
- with(alice.network) {
- val request = TestRequest(sessionId, myAddress)
- val msg = createMessage(TopicSession(dummyTopic), data = request.serialize().bytes)
- send(msg, serviceAddress, retryId = request.sessionID)
- }
-
- // Wait until the first request is received
- crashingNodes.firstRequestReceived.await(5, TimeUnit.SECONDS)
- // Stop alice's node after we ensured that the first request was delivered and ignored.
- alice.dispose()
- val numberOfRequestsReceived = crashingNodes.requestsReceived.get()
- assertThat(numberOfRequestsReceived).isGreaterThanOrEqualTo(1)
-
- crashingNodes.ignoreRequests = false
-
- // Restart the node and expect a response
- val aliceRestarted = startNode(ALICE.name, configOverrides = mapOf("messageRedeliveryDelaySeconds" to 1)).getOrThrow()
- val response = aliceRestarted.network.onNext(dummyTopic, sessionId).getOrThrow(5.seconds)
-
- assertThat(crashingNodes.requestsReceived.get()).isGreaterThan(numberOfRequestsReceived)
- assertThat(response).isEqualTo(responseMessage)
+ private fun DriverDSLExposedInterface.startAlice(): StartedNode {
+ return startNode(providedName = ALICE.name, customOverrides = mapOf("messageRedeliveryDelaySeconds" to 1))
+ .map { (it as NodeHandle.InProcess).node }
+ .getOrThrow()
}
data class CrashingNodes(
@@ -201,4 +207,4 @@ class P2PMessagingTest : NodeBasedTest() {
@CordaSerializable
private data class TestRequest(override val sessionID: Long = random63BitValue(),
override val replyTo: SingleMessageRecipient) : ServiceRequestMessage
-}
\ No newline at end of file
+}
diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt
deleted file mode 100644
index 07e396de2b..0000000000
--- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package net.corda.services.messaging
-
-import com.nhaarman.mockito_kotlin.doReturn
-import com.nhaarman.mockito_kotlin.whenever
-import net.corda.core.concurrent.CordaFuture
-import net.corda.core.crypto.random63BitValue
-import net.corda.core.identity.CordaX500Name
-import net.corda.core.node.NodeInfo
-import net.corda.core.internal.cert
-import net.corda.core.utilities.getOrThrow
-import net.corda.core.utilities.seconds
-import net.corda.node.internal.NetworkMapInfo
-import net.corda.node.services.config.ActiveMqServerConfiguration
-import net.corda.node.services.config.BridgeConfiguration
-import net.corda.node.services.config.configureWithDevSSLCertificate
-import net.corda.node.services.messaging.sendRequest
-import net.corda.node.services.network.NetworkMapService
-import net.corda.node.services.network.NetworkMapService.RegistrationRequest
-import net.corda.node.services.network.NodeRegistration
-import net.corda.node.utilities.AddOrRemove
-import net.corda.testing.*
-import net.corda.testing.node.NodeBasedTest
-import net.corda.testing.node.SimpleNode
-import org.assertj.core.api.Assertions.assertThatExceptionOfType
-import org.assertj.core.api.Assertions.assertThatThrownBy
-import org.junit.Test
-import java.security.cert.X509Certificate
-import java.time.Instant
-import java.util.concurrent.TimeoutException
-
-class P2PSecurityTest : NodeBasedTest() {
-
- @Test
- fun `incorrect legal name for the network map service config`() {
- val incorrectNetworkMapName = CordaX500Name(organisation = "NetworkMap-${random63BitValue()}",
- locality = "London", country = "GB")
- val node = startNode(BOB.name, configOverrides = mapOf(
- "networkMapService" to mapOf(
- "address" to networkMapNode.internals.configuration.p2pAddress.toString(),
- "legalName" to incorrectNetworkMapName.toString()
- )
- ))
- // The connection will be rejected as the legal name doesn't match
- assertThatThrownBy { node.getOrThrow() }.hasMessageContaining(incorrectNetworkMapName.toString())
- }
-
- @Test
- fun `register with the network map service using a legal name different from the TLS CN`() {
- startSimpleNode(DUMMY_BANK_A.name, DEV_TRUST_ROOT.cert).use {
- // Register with the network map using a different legal name
- val response = it.registerWithNetworkMap(DUMMY_BANK_B.name)
- // We don't expect a response because the network map's host verification will prevent a connection back
- // to the attacker as the TLS CN will not match the legal name it has just provided
- assertThatExceptionOfType(TimeoutException::class.java).isThrownBy {
- response.getOrThrow(2.seconds)
- }
- }
- }
-
- private fun startSimpleNode(legalName: CordaX500Name,
- trustRoot: X509Certificate): SimpleNode {
- val config = testNodeConfiguration(
- baseDirectory = baseDirectory(legalName),
- myLegalName = legalName).also {
- doReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)).whenever(it).networkMapService
- doReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))).whenever(it).activeMQServer
- }
- config.configureWithDevSSLCertificate() // This creates the node's TLS cert with the CN as the legal name
- return SimpleNode(config, trustRoot = trustRoot).apply { start() }
- }
-
- private fun SimpleNode.registerWithNetworkMap(registrationName: CordaX500Name): CordaFuture {
- val legalIdentity = getTestPartyAndCertificate(registrationName, identity.public)
- val nodeInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(legalIdentity), 1, serial = 1)
- val registration = NodeRegistration(nodeInfo, System.currentTimeMillis(), AddOrRemove.ADD, Instant.MAX)
- val request = RegistrationRequest(registration.toWire(keyService, identity.public), network.myAddress)
- return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress)
- }
-}
diff --git a/node/src/main/kotlin/net/corda/node/ArgsParser.kt b/node/src/main/kotlin/net/corda/node/ArgsParser.kt
index 7139055566..3de3f30071 100644
--- a/node/src/main/kotlin/net/corda/node/ArgsParser.kt
+++ b/node/src/main/kotlin/net/corda/node/ArgsParser.kt
@@ -4,8 +4,7 @@ import joptsimple.OptionParser
import joptsimple.util.EnumConverter
import net.corda.core.internal.div
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 org.slf4j.event.Level
import java.io.PrintStream
import java.nio.file.Path
@@ -71,6 +70,5 @@ data class CmdLineOptions(val baseDirectory: Path,
val sshdServer: Boolean,
val justGenerateNodeInfo: Boolean) {
fun loadConfig() = ConfigHelper
- .loadConfig(baseDirectory, configFile)
- .parseAs()
+ .loadConfig(baseDirectory, configFile).parseAsNodeConfiguration()
}
diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
index f7adde2604..a9af07e7ad 100644
--- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
@@ -12,14 +12,10 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.*
-import net.corda.core.internal.concurrent.doneFuture
-import net.corda.core.internal.concurrent.flatMap
+import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.*
-import net.corda.core.node.AppServiceHub
-import net.corda.core.node.NodeInfo
-import net.corda.core.node.ServiceHub
-import net.corda.core.node.StateLoader
+import net.corda.core.node.*
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
@@ -90,7 +86,6 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
* Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally
* sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub.
*/
-// TODO: Where this node is the initial network map service, currently no networkMapService is provided.
// In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
abstract class AbstractNode(config: NodeConfiguration,
@@ -111,7 +106,6 @@ abstract class AbstractNode(config: NodeConfiguration,
override val checkpointStorage: CheckpointStorage,
override val smm: StateMachineManager,
override val attachments: NodeAttachmentService,
- override val inNodeNetworkMapService: NetworkMapService,
override val network: MessagingService,
override val database: CordaPersistence,
override val rpcOps: CordaRPCOps,
@@ -119,14 +113,8 @@ abstract class AbstractNode(config: NodeConfiguration,
internal val schedulerService: NodeSchedulerService) : StartedNode {
override val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by services, FlowStarter by flowStarter {}
}
- // TODO: Persist this, as well as whether the node is registered.
- /**
- * Sequence number of changes sent to the network map service, when registering/de-registering this node.
- */
- var networkMapSeq: Long = 1
protected abstract val log: Logger
- protected abstract val networkMapAddress: SingleMessageRecipient?
// We will run as much stuff in this single thread as possible to keep the risk of thread safety bugs low during the
// low-performance prototyping period.
@@ -146,7 +134,6 @@ abstract class AbstractNode(config: NodeConfiguration,
protected lateinit var smm: StateMachineManager
private lateinit var tokenizableServices: List
protected lateinit var attachments: NodeAttachmentService
- protected lateinit var inNodeNetworkMapService: NetworkMapService
protected lateinit var network: MessagingService
protected val runOnStop = ArrayList<() -> Any?>()
protected lateinit var database: CordaPersistence
@@ -232,10 +219,12 @@ abstract class AbstractNode(config: NodeConfiguration,
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
runOnStop += network::stop
- StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps, flowStarter, schedulerService)
+ StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, schedulerService)
}
// If we successfully loaded network data from database, we set this future to Unit.
- _nodeReadyFuture.captureLater(registerWithNetworkMapIfConfigured())
+ services.networkMapCache.addNode(info)
+ _nodeReadyFuture.captureLater(services.networkMapCache.nodeReady.map { Unit })
+
return startedImpl.apply {
database.transaction {
smm.start(tokenizableServices)
@@ -492,7 +481,7 @@ abstract class AbstractNode(config: NodeConfiguration,
services.auditService, services.monitoringService, networkMapCache, services.schemaService,
services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService,
services, cordappProvider, this)
- makeNetworkServices(network, networkMapCache, tokenizableServices)
+ makeNetworkServices(tokenizableServices)
return tokenizableServices
}
@@ -553,16 +542,7 @@ abstract class AbstractNode(config: NodeConfiguration,
}
}
- private fun setupInNodeNetworkMapService(networkMapCache: NetworkMapCacheInternal) {
- inNodeNetworkMapService =
- if (configuration.networkMapService == null && !configuration.noNetworkMapServiceMode)
- makeNetworkMapService(network, networkMapCache)
- else
- NullNetworkMapService
- }
-
- private fun makeNetworkServices(network: MessagingService, networkMapCache: NetworkMapCacheInternal, tokenizableServices: MutableList) {
- setupInNodeNetworkMapService(networkMapCache)
+ private fun makeNetworkServices(tokenizableServices: MutableList) {
configuration.notary?.let {
val notaryService = makeCoreNotaryService(it)
tokenizableServices.add(notaryService)
@@ -573,40 +553,6 @@ abstract class AbstractNode(config: NodeConfiguration,
}
}
- private fun registerWithNetworkMapIfConfigured(): CordaFuture {
- services.networkMapCache.addNode(info)
- // In the unit test environment, we may sometimes run without any network map service
- return if (networkMapAddress == null && inNodeNetworkMapService == NullNetworkMapService) {
- services.networkMapCache.runWithoutMapService()
- noNetworkMapConfigured() // TODO This method isn't needed as runWithoutMapService sets the Future in the cache
- } else {
- val netMapRegistration = registerWithNetworkMap()
- // We may want to start node immediately with database data and not wait for network map registration (but send it either way).
- // So we are ready to go.
- if (services.networkMapCache.loadDBSuccess) {
- log.info("Node successfully loaded network map data from the database.")
- doneFuture(Unit)
- } else {
- netMapRegistration
- }
- }
- }
-
- /**
- * Register this node with the network map cache, and load network map from a remote service (and register for
- * updates) if one has been supplied.
- */
- protected open fun registerWithNetworkMap(): CordaFuture {
- val address: SingleMessageRecipient = networkMapAddress ?:
- network.getAddressOfParty(PartyInfo.SingleNode(services.myInfo.legalIdentitiesAndCerts.first().party, info.addresses)) as SingleMessageRecipient
- // Register for updates, even if we're the one running the network map.
- return sendNetworkMapRegistration(address).flatMap { (error) ->
- check(error == null) { "Unable to register with the network map service: $error" }
- // The future returned addMapService will complete on the same executor as sendNetworkMapRegistration, namely the one used by net
- services.networkMapCache.addMapService(network, address, true, null)
- }
- }
-
private fun sendNetworkMapRegistration(networkMapAddress: SingleMessageRecipient): CordaFuture {
// Register this node against the network
val instant = platformClock.instant()
@@ -619,14 +565,10 @@ abstract class AbstractNode(config: NodeConfiguration,
/** Return list of node's addresses. It's overridden in MockNetwork as we don't have real addresses for MockNodes. */
protected abstract fun myAddresses(): List
- /** This is overriden by the mock node implementation to enable operation without any network map service */
- protected open fun noNetworkMapConfigured(): CordaFuture {
- if (services.networkMapCache.loadDBSuccess || configuration.noNetworkMapServiceMode) {
- return doneFuture(Unit)
- } else {
+ open protected fun checkNetworkMapIsInitialized() {
+ if (!services.networkMapCache.loadDBSuccess ) {
// TODO: There should be a consistent approach to configuration error exceptions.
- throw IllegalStateException("Configuration error: this node isn't being asked to act as the network map, nor " +
- "has any other map node been configured.")
+ throw NetworkMapCacheEmptyException()
}
}
@@ -803,9 +745,9 @@ abstract class AbstractNode(config: NodeConfiguration,
return flowFactories[initiatingFlowClass]
}
- override fun recordTransactions(notifyVault: Boolean, txs: Iterable) {
+ override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) {
database.transaction {
- super.recordTransactions(notifyVault, txs)
+ super.recordTransactions(statesToRecord, txs)
}
}
@@ -818,3 +760,8 @@ internal class FlowStarterImpl(private val serverThread: AffinityExecutor, priva
return serverThread.fetchFrom { smm.startFlow(logic, flowInitiator, ourIdentity) }
}
}
+
+/**
+ * Thrown when a node is about to start and its network map cache doesn't contain any node.
+ */
+internal class NetworkMapCacheEmptyException: Exception()
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt
index 8b28ed9667..00f376726e 100644
--- a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt
@@ -5,21 +5,21 @@ import com.jcraft.jsch.JSchException
import net.corda.core.internal.Emoji
import net.corda.core.utilities.loggerFor
import net.corda.node.VersionInfo
-import net.corda.node.services.config.FullNodeConfiguration
+import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.RelayConfiguration
import net.corda.nodeapi.internal.ServiceInfo
import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole
import java.io.IOException
-class EnterpriseNode(configuration: FullNodeConfiguration,
+class EnterpriseNode(configuration: NodeConfiguration,
versionInfo: VersionInfo) : Node(configuration, versionInfo) {
companion object {
private val logger by lazy { loggerFor() }
}
class Startup(args: Array) : NodeStartup(args) {
- override fun preNetworkRegistration(conf: FullNodeConfiguration) {
+ override fun preNetworkRegistration(conf: NodeConfiguration) {
super.preNetworkRegistration(conf)
conf.relay?.let { connectToRelay(it, conf.p2pAddress.port) }
}
@@ -66,7 +66,7 @@ D""".trimStart()
return tips[(Math.random() * tips.size).toInt()]
}
- override fun createNode(conf: FullNodeConfiguration, versionInfo: VersionInfo) = EnterpriseNode(conf, versionInfo)
+ override fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo) = EnterpriseNode(conf, versionInfo)
private fun connectToRelay(config: RelayConfiguration, localBrokerPort: Int) {
with(config) {
diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt
index 359a80ecd9..c75ac90ce0 100644
--- a/node/src/main/kotlin/net/corda/node/internal/Node.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt
@@ -5,8 +5,6 @@ import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
-import net.corda.core.internal.concurrent.doneFuture
-import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.uncheckedCast
@@ -22,7 +20,6 @@ import net.corda.node.services.RPCUserService
import net.corda.node.services.RPCUserServiceImpl
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.api.SchemaService
-import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.node.services.messaging.ArtemisMessagingServer.Companion.ipDetectRequestProperty
@@ -37,7 +34,6 @@ import net.corda.node.utilities.TestClock
import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.IP_REQUEST_PREFIX
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
-import net.corda.nodeapi.ArtemisMessagingComponent.NetworkMapAddress
import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.internal.ShutdownHook
@@ -62,7 +58,7 @@ import kotlin.system.exitProcess
*
* @param configuration This is typically loaded from a TypeSafe HOCON configuration file.
*/
-open class Node(configuration: FullNodeConfiguration,
+open class Node(override val configuration: NodeConfiguration,
versionInfo: VersionInfo,
val initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration)
@@ -84,7 +80,7 @@ open class Node(configuration: FullNodeConfiguration,
exitProcess(1)
}
- private fun createClock(configuration: FullNodeConfiguration): Clock {
+ private fun createClock(configuration: NodeConfiguration): Clock {
return if (configuration.useTestClock) TestClock() else NodeClock()
}
@@ -99,8 +95,6 @@ open class Node(configuration: FullNodeConfiguration,
}
override val log: Logger get() = logger
- override val configuration get() = super.configuration as FullNodeConfiguration // Necessary to avoid init order NPE.
- override val networkMapAddress: NetworkMapAddress? get() = configuration.networkMapService?.address?.let(::NetworkMapAddress)
override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService
private val sameVmNodeNumber = sameVmNodeCounter.incrementAndGet() // Under normal (non-test execution) it will always be "1"
@@ -153,23 +147,16 @@ open class Node(configuration: FullNodeConfiguration,
override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService {
userService = RPCUserServiceImpl(configuration.rpcUsers)
- val (serverAddress, advertisedAddress) = with(configuration) {
- if (messagingServerAddress != null) {
- // External broker
- messagingServerAddress to messagingServerAddress
- } else {
- makeLocalMessageBroker() to getAdvertisedAddress()
- }
- }
+ val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
+ val advertisedAddress = configuration.messagingServerAddress ?: getAdvertisedAddress()
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
- val myIdentityOrNullIfNetworkMapService = if (networkMapAddress != null) legalIdentity.owningKey else null
return NodeMessagingClient(
configuration,
versionInfo,
serverAddress,
- myIdentityOrNullIfNetworkMapService,
+ legalIdentity.owningKey,
serverThread,
database,
nodeReadyFuture,
@@ -187,7 +174,7 @@ open class Node(configuration: FullNodeConfiguration,
private fun getAdvertisedAddress(): NetworkHostAndPort {
return with(configuration) {
if (relay != null) {
- NetworkHostAndPort(relay.relayHost, relay.remoteInboundPort)
+ NetworkHostAndPort(relay!!.relayHost, relay!!.remoteInboundPort)
} else {
val useHost = if (detectPublicIp) {
tryDetectIfNotPublicHost(p2pAddress.host) ?: p2pAddress.host
@@ -201,16 +188,14 @@ open class Node(configuration: FullNodeConfiguration,
/**
* Checks whether the specified [host] is a public IP address or hostname. If not, tries to discover the current
- * machine's public IP address to be used instead. It first looks through the network interfaces, and if no public IP
- * is found, asks the network map service to provide it.
+ * machine's public IP address to be used instead by looking through the network interfaces.
+ * TODO this code used to rely on the networkmap node, we might want to look at a different solution.
*/
private fun tryDetectIfNotPublicHost(host: String): String? {
if (!AddressUtils.isPublic(host)) {
val foundPublicIP = AddressUtils.tryDetectPublicIP()
- if (foundPublicIP == null) {
- networkMapAddress?.let { return discoverPublicHost(it.hostAndPort) }
- } else {
+ if (foundPublicIP != null) {
log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.")
return foundPublicIP.hostAddress
}
@@ -276,15 +261,6 @@ open class Node(configuration: FullNodeConfiguration,
(network as NodeMessagingClient).start(rpcOps, userService)
}
- /**
- * Insert an initial step in the registration process which will throw an exception if a non-recoverable error is
- * encountered when trying to connect to the network map node.
- */
- override fun registerWithNetworkMap(): CordaFuture {
- val networkMapConnection = messageBroker?.networkMapConnectionFuture ?: doneFuture(Unit)
- return networkMapConnection.flatMap { super.registerWithNetworkMap() }
- }
-
override fun myAddresses(): List {
val address = network.myAddress as ArtemisMessagingComponent.ArtemisPeerAddress
return listOf(address.hostAndPort)
diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
index 1a52cbcadc..16de351f63 100644
--- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
@@ -1,16 +1,13 @@
package net.corda.node.internal
import com.jcabi.manifests.Manifests
-import com.jcraft.jsch.JSch
-import com.jcraft.jsch.JSchException
import com.typesafe.config.ConfigException
import joptsimple.OptionException
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.utilities.loggerFor
import net.corda.node.*
-import net.corda.node.services.config.FullNodeConfiguration
-import net.corda.node.services.config.RelayConfiguration
+import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.transactions.bftSMaRtSerialFilter
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
@@ -20,7 +17,6 @@ import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole
import org.slf4j.bridge.SLF4JBridgeHandler
import sun.misc.VMSupport
-import java.io.IOException
import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
import java.net.InetAddress
@@ -88,11 +84,11 @@ open class NodeStartup(val args: Array) {
exitProcess(0)
}
- open protected fun preNetworkRegistration(conf: FullNodeConfiguration) = Unit
+ open protected fun preNetworkRegistration(conf: NodeConfiguration) = Unit
- open protected fun createNode(conf: FullNodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
+ open protected fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
- open protected fun startNode(conf: FullNodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) {
+ open protected fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) {
val node = createNode(conf, versionInfo)
if (cmdlineOptions.justGenerateNodeInfo) {
// Perform the minimum required start-up logic to be able to write a nodeInfo to disk
@@ -122,14 +118,14 @@ open class NodeStartup(val args: Array) {
startedNode.internals.run()
}
- open protected fun logStartupInfo(versionInfo: VersionInfo, cmdlineOptions: CmdLineOptions, conf: FullNodeConfiguration) {
+ open protected fun logStartupInfo(versionInfo: VersionInfo, cmdlineOptions: CmdLineOptions, conf: NodeConfiguration) {
logger.info("Vendor: ${versionInfo.vendor}")
logger.info("Release: ${versionInfo.releaseVersion}")
logger.info("Platform Version: ${versionInfo.platformVersion}")
logger.info("Revision: ${versionInfo.revision}")
val info = ManagementFactory.getRuntimeMXBean()
logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
- logger.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
+ logger.info("Main class: ${NodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
logger.info("Application Args: ${args.joinToString(" ")}")
logger.info("bootclasspath: ${info.bootClassPath}")
@@ -144,7 +140,7 @@ open class NodeStartup(val args: Array) {
logger.info("Starting as node on ${conf.p2pAddress}")
}
- open protected fun maybeRegisterWithNetworkAndExit(cmdlineOptions: CmdLineOptions, conf: FullNodeConfiguration) {
+ open protected fun maybeRegisterWithNetworkAndExit(cmdlineOptions: CmdLineOptions, conf: NodeConfiguration) {
if (!cmdlineOptions.isRegistration) return
println()
println("******************************************************************")
@@ -156,7 +152,7 @@ open class NodeStartup(val args: Array) {
exitProcess(0)
}
- open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): FullNodeConfiguration {
+ open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration {
try {
return cmdlineOptions.loadConfig()
} catch (e: ConfigException) {
@@ -165,7 +161,7 @@ open class NodeStartup(val args: Array) {
}
}
- open protected fun banJavaSerialisation(conf: FullNodeConfiguration) {
+ open protected fun banJavaSerialisation(conf: NodeConfiguration) {
SerialFilter.install(if (conf.notary?.bftSMaRt != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
}
diff --git a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt
index 71b80d5aea..c948a1f3c6 100644
--- a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt
@@ -13,7 +13,6 @@ import net.corda.core.serialization.SerializeAsToken
import net.corda.node.services.api.CheckpointStorage
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.messaging.MessagingService
-import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.utilities.CordaPersistence
@@ -25,7 +24,6 @@ interface StartedNode {
val checkpointStorage: CheckpointStorage
val smm: StateMachineManager
val attachments: NodeAttachmentService
- val inNodeNetworkMapService: NetworkMapService
val network: MessagingService
val database: CordaPersistence
val rpcOps: CordaRPCOps
diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt
index 5154c592b1..50daaa1d8d 100644
--- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt
@@ -274,6 +274,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List() {
@Suspendable
override fun call() {
- val stx = subFlow(ReceiveTransactionFlow(sender))
- serviceHub.recordTransactions(stx)
+ subFlow(ReceiveTransactionFlow(sender, true, StatesToRecord.ONLY_RELEVANT))
}
}
diff --git a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt
index 2c4ed57754..58a50dceb7 100644
--- a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt
@@ -18,6 +18,12 @@ interface RPCUserService {
// TODO Or ditch this and consider something like Apache Shiro
// TODO Need access to permission checks from inside flows and at other point during audit checking.
class RPCUserServiceImpl(override val users: List) : RPCUserService {
+ init {
+ users.forEach {
+ require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" }
+ }
+ }
+
override fun getUser(username: String): User? = users.find { it.username == username }
}
diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
index de12960304..f2d888d54d 100644
--- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
+++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
@@ -11,10 +11,10 @@ import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.DataFeed
-import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
+import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCacheBase
import net.corda.core.node.services.TransactionStorage
@@ -32,35 +32,12 @@ import net.corda.node.utilities.CordaPersistence
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal
interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
- /**
- * Deregister from updates from the given map service.
- * @param network the network messaging service.
- * @param mapParty the network map service party to fetch current state from.
- */
- fun deregisterForUpdates(network: MessagingService, mapParty: Party): CordaFuture
-
- /**
- * Add a network map service; fetches a copy of the latest map from the service and subscribes to any further
- * updates.
- * @param network the network messaging service.
- * @param networkMapAddress the network map service to fetch current state from.
- * @param subscribe if the cache should subscribe to updates.
- * @param ifChangedSinceVer an optional version number to limit updating the map based on. If the latest map
- * version is less than or equal to the given version, no update is fetched.
- */
- fun addMapService(network: MessagingService, networkMapAddress: SingleMessageRecipient,
- subscribe: Boolean, ifChangedSinceVer: Int? = null): CordaFuture
-
/** Adds a node to the local cache (generally only used for adding ourselves). */
fun addNode(node: NodeInfo)
/** Removes a node from the local cache. */
fun removeNode(node: NodeInfo)
- /** For testing where the network map cache is manipulated marks the service as immediately ready. */
- @VisibleForTesting
- fun runWithoutMapService()
-
/** Indicates if loading network map data from database was successful. */
val loadDBSuccess: Boolean
}
@@ -93,7 +70,7 @@ interface ServiceHubInternal : ServiceHub {
val database: CordaPersistence
val configuration: NodeConfiguration
override val cordappProvider: CordappProviderInternal
- override fun recordTransactions(notifyVault: Boolean, txs: Iterable) {
+ override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) {
require(txs.any()) { "No transactions passed in for recording" }
val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) }
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
@@ -105,9 +82,43 @@ interface ServiceHubInternal : ServiceHub {
log.warn("Transactions recorded from outside of a state machine")
}
- if (notifyVault) {
+ if (statesToRecord != StatesToRecord.NONE) {
val toNotify = recordedTransactions.map { if (it.isNotaryChangeTransaction()) it.notaryChangeTx else it.tx }
- vaultService.notifyAll(toNotify)
+ // When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
+ // that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
+ // is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
+ //
+ // The reason for this is three-fold:
+ //
+ // 1) We are putting in place the observer mode feature relatively quickly to meet specific customer
+ // launch target dates.
+ //
+ // 2) The right design for vaults which mix observations and relevant states isn't entirely clear yet.
+ //
+ // 3) If we get the design wrong it could create security problems and business confusions.
+ //
+ // Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the
+ // Bitcoin equivalent of observer nodes:
+ //
+ // https://bitcoinj.github.io/working-with-the-wallet#watching-wallets
+ //
+ // The ability to have a wallet containing both irrelevant and relevant states complicated everything quite
+ // dramatically, even methods as basic as the getBalance() API which required additional modes to let you
+ // query "balance I can spend" vs "balance I am observing". In the end it might have been better to just
+ // require the user to create an entirely separate wallet for observing with.
+ //
+ // In Corda we don't support a single node having multiple vaults (at the time of writing), and it's not
+ // clear that's the right way to go: perhaps adding an "origin" column to the VAULT_STATES table is a better
+ // solution. Then you could select subsets of states depending on where the report came from.
+ //
+ // The risk of doing this is that apps/developers may use 'canned SQL queries' not written by us that forget
+ // to add a WHERE clause for the origin column. Those queries will seem to work most of the time until
+ // they're run on an observer node and mix in irrelevant data. In the worst case this may result in
+ // erroneous data being reported to the user, which could cause security problems.
+ //
+ // Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
+ // to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
+ vaultService.notifyAll(statesToRecord, toNotify)
}
}
diff --git a/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt
index fde38dfe05..b24978f202 100644
--- a/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt
+++ b/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt
@@ -1,5 +1,6 @@
package net.corda.node.services.api
+import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.VaultService
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
@@ -12,8 +13,8 @@ interface VaultServiceInternal : VaultService {
* indicate whether an update consists entirely of regular or notary change transactions, which may require
* different processing logic.
*/
- fun notifyAll(txns: Iterable)
+ fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable)
/** Same as notifyAll but with a single transaction. */
- fun notify(tx: CoreTransaction) = notifyAll(listOf(tx))
+ fun notify(statesToRecord: StatesToRecord, tx: CoreTransaction) = notifyAll(statesToRecord, listOf(tx))
}
diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
index 88f2dd855e..bfc0947bd2 100644
--- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
+++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
@@ -1,26 +1,23 @@
package net.corda.node.services.config
+import com.typesafe.config.Config
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
-import net.corda.node.internal.NetworkMapInfo
import net.corda.node.services.messaging.CertificateChainCheckPolicy
import net.corda.nodeapi.User
import net.corda.nodeapi.config.NodeSSLConfiguration
+import net.corda.nodeapi.config.parseAs
import java.net.URL
import java.nio.file.Path
import java.util.*
+data class DevModeOptions(val disableCheckpointChecker: Boolean = false)
+
interface NodeConfiguration : NodeSSLConfiguration {
// myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this.
// TODO: Remove this so we don't accidentally use this identity in the code?
val myLegalName: CordaX500Name
- /**
- * If null then configure the node to run as the netwok map service, otherwise use this to connect to the network map
- * service.
- */
- val networkMapService: NetworkMapInfo?
- val noNetworkMapServiceMode: Boolean
val minimumPlatformVersion: Int
val emailAddress: String
val exportJMXto: String
@@ -28,6 +25,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
val database: Properties?
val rpcUsers: List
val devMode: Boolean
+ val devModeOptions: DevModeOptions?
val certificateSigningService: URL
val certificateChainCheckPolicies: List
val verifierType: VerifierType
@@ -35,6 +33,14 @@ interface NodeConfiguration : NodeSSLConfiguration {
val notary: NotaryConfig?
val activeMQServer: ActiveMqServerConfiguration
val additionalNodeInfoPollingFrequencyMsec: Long
+ val useHTTPS: Boolean
+ val p2pAddress: NetworkHostAndPort
+ val rpcAddress: NetworkHostAndPort?
+ val messagingServerAddress: NetworkHostAndPort?
+ // TODO Move into DevModeOptions
+ val useTestClock: Boolean get() = false
+ val detectPublicIp: Boolean get() = true
+ val relay: RelayConfiguration?
}
data class NotaryConfig(val validating: Boolean,
@@ -68,7 +74,9 @@ data class BridgeConfiguration(val retryIntervalMs: Long,
data class ActiveMqServerConfiguration(val bridge: BridgeConfiguration)
-data class FullNodeConfiguration(
+fun Config.parseAsNodeConfiguration(): NodeConfiguration = this.parseAs()
+
+data class NodeConfigurationImpl(
/** This is not retrieved from the config file but rather from a command line argument. */
override val baseDirectory: Path,
override val myLegalName: CordaX500Name,
@@ -78,25 +86,27 @@ data class FullNodeConfiguration(
override val dataSourceProperties: Properties,
override val database: Properties?,
override val certificateSigningService: URL,
- override val networkMapService: NetworkMapInfo?,
- override val noNetworkMapServiceMode: Boolean = false,
override val minimumPlatformVersion: Int = 1,
override val rpcUsers: List,
override val verifierType: VerifierType,
+ // TODO typesafe config supports the notion of durations. Make use of that by mapping it to java.time.Duration.
+ // Then rename this to messageRedeliveryDelay and make it of type Duration
override val messageRedeliveryDelaySeconds: Int = 30,
- val useHTTPS: Boolean,
- val p2pAddress: NetworkHostAndPort,
- val rpcAddress: NetworkHostAndPort?,
- val relay: RelayConfiguration?,
+ override val useHTTPS: Boolean,
+ override val p2pAddress: NetworkHostAndPort,
+ override val rpcAddress: NetworkHostAndPort?,
+ override val relay: RelayConfiguration?,
// TODO This field is slightly redundant as p2pAddress is sufficient to hold the address of the node's MQ broker.
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
- val messagingServerAddress: NetworkHostAndPort?,
+ override val messagingServerAddress: NetworkHostAndPort?,
override val notary: NotaryConfig?,
override val certificateChainCheckPolicies: List,
override val devMode: Boolean = false,
- val useTestClock: Boolean = false,
- val detectPublicIp: Boolean = true,
+ override val devModeOptions: DevModeOptions? = null,
+ override val useTestClock: Boolean = false,
+ override val detectPublicIp: Boolean = true,
override val activeMQServer: ActiveMqServerConfiguration,
+ // TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis()
) : NodeConfiguration {
override val exportJMXto: String get() = "http"
@@ -104,10 +114,7 @@ data class FullNodeConfiguration(
init {
// This is a sanity feature do not remove.
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
- // TODO Move this to ArtemisMessagingServer
- rpcUsers.forEach {
- require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" }
- }
+ require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" }
require(minimumPlatformVersion >= 1) { "minimumPlatformVersion cannot be less than 1" }
}
diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt
index 18d074cd68..c948f27732 100644
--- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt
@@ -11,8 +11,8 @@ import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
+import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.utilities.AppendOnlyPersistentMap
-import net.corda.node.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.utilities.NODE_DATABASE_PREFIX
import org.bouncycastle.cert.X509CertificateHolder
import java.io.ByteArrayInputStream
diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt
index 1c19ce0acb..135d320266 100644
--- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt
@@ -5,8 +5,8 @@ import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SingletonSerializeAsToken
+import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.utilities.AppendOnlyPersistentMap
-import net.corda.node.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.utilities.NODE_DATABASE_PREFIX
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
@@ -50,7 +50,10 @@ class PersistentKeyManagementService(val identityService: IdentityService,
fun createKeyMap(): AppendOnlyPersistentMap {
return AppendOnlyPersistentMap(
toPersistentEntityKey = { it.toStringShort() },
- fromPersistentEntity = { Pair(Crypto.decodePublicKey(it.publicKey), Crypto.decodePrivateKey(it.privateKey)) },
+ fromPersistentEntity = {
+ Pair(Crypto.decodePublicKey(it.publicKey),
+ Crypto.decodePrivateKey(it.privateKey))
+ },
toPersistentEntity = { key: PublicKey, value: PrivateKey ->
PersistentKey(key, value)
},
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt
index 52421eb6d1..12951716c9 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt
@@ -1,21 +1,20 @@
package net.corda.node.services.messaging
-import com.google.common.util.concurrent.ListenableFuture
import io.netty.handler.ssl.SslHandler
-import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.AddressFormatException
import net.corda.core.crypto.newSecureRandom
-import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.ThreadBox
-import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCache.MapChange
-import net.corda.core.utilities.*
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.debug
+import net.corda.core.utilities.loggerFor
+import net.corda.core.utilities.parsePublicKeyBase58
import net.corda.node.internal.Node
import net.corda.node.services.RPCUserService
import net.corda.node.services.config.NodeConfiguration
@@ -37,12 +36,10 @@ import org.apache.activemq.artemis.core.config.Configuration
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
-import org.apache.activemq.artemis.core.message.impl.CoreMessage
import org.apache.activemq.artemis.core.remoting.impl.netty.*
import org.apache.activemq.artemis.core.security.Role
import org.apache.activemq.artemis.core.server.ActiveMQServer
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
-import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
import org.apache.activemq.artemis.core.settings.impl.AddressSettings
import org.apache.activemq.artemis.spi.core.remoting.*
@@ -111,14 +108,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
private val mutex = ThreadBox(InnerState())
private lateinit var activeMQServer: ActiveMQServer
val serverControl: ActiveMQServerControl get() = activeMQServer.activeMQServerControl
- private val _networkMapConnectionFuture = config.networkMapService?.let { openFuture() }
- /**
- * A [ListenableFuture] which completes when the server successfully connects to the network map node. If a
- * non-recoverable error is encountered then the Future will complete with an exception.
- */
- val networkMapConnectionFuture: CordaFuture? get() = _networkMapConnectionFuture
private var networkChangeHandle: Subscription? = null
- private val nodeRunsNetworkMapService = config.networkMapService == null
init {
config.baseDirectory.requireOnDefaultFileSystem()
@@ -132,8 +122,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
fun start() = mutex.locked {
if (!running) {
configureAndStartServer()
- // Deploy bridge to the network map service
- config.networkMapService?.let { deployBridge(NetworkMapAddress(it.address), setOf(it.legalName)) }
networkChangeHandle = networkMapCache.changed.subscribe { updateBridgesOnNetworkChange(it) }
running = true
}
@@ -158,7 +146,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
// Some types of queue might need special preparation on our side, like dialling back or preparing
// a lazily initialised subsystem.
registerPostQueueCreationCallback { deployBridgesFromNewQueue(it.toString()) }
- if (nodeRunsNetworkMapService) registerPostQueueCreationCallback { handleIpDetectionRequest(it.toString()) }
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
}
activeMQServer.start()
@@ -247,12 +234,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(RPC_ROLE, send = true))
// TODO remove the NODE_USER role once the webserver doesn't need it
securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$NODE_USER.#"] = setOf(nodeInternalRole)
- if (nodeRunsNetworkMapService) {
- securityRoles["$IP_REQUEST_PREFIX*"] = setOf(
- nodeInternalRole,
- restrictedRole(PEER_ROLE, consume = true, createNonDurableQueue = true, deleteNonDurableQueue = true)
- )
- }
for ((username) in userService.users) {
securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#"] = setOf(
nodeInternalRole,
@@ -330,7 +311,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
log.debug { "Updating bridges on network map change: ${change.node}" }
fun gatherAddresses(node: NodeInfo): Sequence {
val address = node.addresses.first()
- return node.legalIdentitiesAndCerts.map { getArtemisPeerAddress(it.party, address, config.networkMapService?.legalName) }.asSequence()
+ return node.legalIdentitiesAndCerts.map { NodeAddress(it.party.owningKey, address) }.asSequence()
}
fun deployBridges(node: NodeInfo) {
@@ -409,47 +390,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
private val ArtemisPeerAddress.bridgeName: String get() = getBridgeName(queueName, hostAndPort)
private fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
-
- // This is called on one of Artemis' background threads
- internal fun hostVerificationFail(expectedLegalNames: Set, errorMsg: String?) {
- log.error(errorMsg)
- if (config.networkMapService?.legalName in expectedLegalNames) {
- // If the peer that failed host verification was the network map node then we're in big trouble and need to bail!
- _networkMapConnectionFuture!!.setException(IOException("${config.networkMapService} failed host verification check"))
- }
- }
-
- // This is called on one of Artemis' background threads
- internal fun onTcpConnection(peerLegalName: CordaX500Name) {
- if (peerLegalName == config.networkMapService?.legalName) {
- _networkMapConnectionFuture!!.set(Unit)
- }
- }
-
- private fun handleIpDetectionRequest(queueName: String) {
- fun getRemoteAddress(requestId: String): String? {
- val session = activeMQServer.sessions.first {
- it.getMetaData(ipDetectRequestProperty) == requestId
- }
- return session.remotingConnection.remoteAddress
- }
-
- fun sendResponse(remoteAddress: String?) {
- val responseMessage = CoreMessage(random63BitValue(), 0).apply {
- putStringProperty(ipDetectResponseProperty, remoteAddress)
- }
- val routingContext = RoutingContextImpl(null)
- val queue = activeMQServer.locateQueue(SimpleString(queueName))
- queue.route(responseMessage, routingContext)
- activeMQServer.postOffice.processRoute(responseMessage, routingContext, true)
- }
-
- if (!queueName.startsWith(IP_REQUEST_PREFIX)) return
- val requestId = queueName.substringAfter(IP_REQUEST_PREFIX)
- val remoteAddress = getRemoteAddress(requestId)
- log.debug { "Detected remote address $remoteAddress for request $requestId" }
- sendResponse(remoteAddress)
- }
}
class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
@@ -473,7 +413,10 @@ private class VerifyingNettyConnector(configuration: MutableMap,
scheduledThreadPool: ScheduledExecutorService?,
protocolManager: ClientProtocolManager?) :
NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) {
- private val server = configuration[ArtemisMessagingServer::class.java.name] as ArtemisMessagingServer
+ companion object {
+ private val log = loggerFor()
+ }
+
private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration)
override fun createConnection(): Connection? {
@@ -504,10 +447,9 @@ private class VerifyingNettyConnector(configuration: MutableMap,
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
}
X509Utilities.validateCertificateChain(session.localCertificates.last() as java.security.cert.X509Certificate, *session.peerCertificates)
- server.onTcpConnection(peerLegalName)
} catch (e: IllegalArgumentException) {
connection.close()
- server.hostVerificationFail(expectedLegalNames, e.message)
+ log.error(e.message)
return null
}
}
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt
index 401709f811..d9b0aa0f13 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt
@@ -48,7 +48,6 @@ import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Lob
-import javax.security.auth.x500.X500Principal
// TODO: Stop the wallet explorer and other clients from using this class and get rid of persistentInbox
@@ -76,7 +75,7 @@ import javax.security.auth.x500.X500Principal
class NodeMessagingClient(override val config: NodeConfiguration,
private val versionInfo: VersionInfo,
private val serverAddress: NetworkHostAndPort,
- private val myIdentity: PublicKey?,
+ private val myIdentity: PublicKey,
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
val database: CordaPersistence,
private val networkMapRegistrationFuture: CordaFuture,
@@ -172,14 +171,7 @@ class NodeMessagingClient(override val config: NodeConfiguration,
/** An executor for sending messages */
private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1)
- /**
- * Apart from the NetworkMapService this is the only other address accessible to the node outside of lookups against the NetworkMapCache.
- */
- override val myAddress: SingleMessageRecipient = if (myIdentity != null) {
- NodeAddress.asSingleNode(myIdentity, advertisedAddress)
- } else {
- NetworkMapAddress(advertisedAddress)
- }
+ override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress)
private val state = ThreadBox(InnerState())
private val handlers = CopyOnWriteArrayList()
@@ -634,9 +626,7 @@ class NodeMessagingClient(override val config: NodeConfiguration,
// TODO Rethink PartyInfo idea and merging PeerAddress/ServiceAddress (the only difference is that Service address doesn't hold host and port)
override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients {
return when (partyInfo) {
- is PartyInfo.SingleNode -> {
- getArtemisPeerAddress(partyInfo.party, partyInfo.addresses.first(), config.networkMapService?.legalName)
- }
+ is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey, partyInfo.addresses.first())
is PartyInfo.DistributedNode -> ServiceAddress(partyInfo.party.owningKey)
}
}
diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt
index d242f3869f..d632da7242 100644
--- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt
+++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt
@@ -1,18 +1,14 @@
package net.corda.node.services.network
import net.corda.core.concurrent.CordaFuture
-import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
-import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.bufferUntilSubscribed
-import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed
-import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkMapCache.MapChange
@@ -21,29 +17,18 @@ import net.corda.core.node.services.PartyInfo
import net.corda.core.schemas.NodeInfoSchemaV1
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SingletonSerializeAsToken
-import net.corda.core.serialization.deserialize
-import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
-import net.corda.core.utilities.toBase58String
-import net.corda.node.services.api.NetworkCacheException
-import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.api.NetworkMapCacheBaseInternal
+import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.config.NodeConfiguration
-import net.corda.node.services.messaging.MessagingService
-import net.corda.node.services.messaging.createMessage
-import net.corda.node.services.messaging.sendRequest
-import net.corda.node.services.network.NetworkMapService.FetchMapResponse
-import net.corda.node.services.network.NetworkMapService.SubscribeResponse
-import net.corda.node.utilities.*
-import net.corda.node.utilities.AddOrRemove
+import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.bufferUntilDatabaseCommit
import net.corda.node.utilities.wrapWithDatabaseTransaction
import org.hibernate.Session
import rx.Observable
import rx.subjects.PublishSubject
import java.security.PublicKey
-import java.security.SignatureException
import java.util.*
import javax.annotation.concurrent.ThreadSafe
import kotlin.collections.HashMap
@@ -78,7 +63,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
val logger = loggerFor()
}
- private var registeredForPush = false
// TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in
// next PR that gets rid of services. These maps are used only for queries by service.
protected val registeredNodes: MutableMap = Collections.synchronizedMap(HashMap())
@@ -88,6 +72,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
override val changed: Observable = _changed.wrapWithDatabaseTransaction()
private val changePublisher: rx.Observer get() = _changed.bufferUntilDatabaseCommit()
+ // TODO revisit the logic under which nodeReady and loadDBSuccess are set.
+ // with the NetworkMapService redesign their meaning is not too well defined.
private val _registrationFuture = openFuture()
override val nodeReady: CordaFuture get() = _registrationFuture
private var _loadDBSuccess: Boolean = false
@@ -152,38 +138,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
}
}
- override fun addMapService(network: MessagingService, networkMapAddress: SingleMessageRecipient, subscribe: Boolean,
- ifChangedSinceVer: Int?): CordaFuture {
- if (subscribe && !registeredForPush) {
- // Add handler to the network, for updates received from the remote network map service.
- network.addMessageHandler(NetworkMapService.PUSH_TOPIC) { message, _ ->
- try {
- val req = message.data.deserialize()
- val ackMessage = network.createMessage(NetworkMapService.PUSH_ACK_TOPIC,
- data = NetworkMapService.UpdateAcknowledge(req.mapVersion, network.myAddress).serialize().bytes)
- network.send(ackMessage, req.replyTo)
- processUpdatePush(req)
- } catch (e: NodeMapException) {
- logger.warn("Failure during node map update due to bad update: ${e.javaClass.name}")
- } catch (e: Exception) {
- logger.error("Exception processing update from network map service", e)
- }
- }
- registeredForPush = true
- }
-
- // Fetch the network map and register for updates at the same time
- val req = NetworkMapService.FetchMapRequest(subscribe, ifChangedSinceVer, network.myAddress)
- val future = network.sendRequest(NetworkMapService.FETCH_TOPIC, req, networkMapAddress).map { (nodes) ->
- // We may not receive any nodes back, if the map hasn't changed since the version specified
- nodes?.forEach { processRegistration(it) }
- Unit
- }
- _registrationFuture.captureLater(future.map { null })
-
- return future
- }
-
override fun addNode(node: NodeInfo) {
logger.info("Adding node with info: $node")
synchronized(_changed) {
@@ -210,6 +164,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
logger.info("Previous node was identical to incoming one - doing nothing")
}
}
+ _loadDBSuccess = true // This is used in AbstractNode to indicate that node is ready.
+ _registrationFuture.set(null)
logger.info("Done adding node with info: $node")
}
@@ -225,49 +181,11 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
logger.info("Done removing node with info: $node")
}
- /**
- * Unsubscribes from updates from the given map service.
- * @param mapParty the network map service party to listen to updates from.
- */
- override fun deregisterForUpdates(network: MessagingService, mapParty: Party): CordaFuture {
- // Fetch the network map and register for updates at the same time
- val req = NetworkMapService.SubscribeRequest(false, network.myAddress)
- // `network.getAddressOfParty(partyInfo)` is a work-around for MockNetwork and InMemoryMessaging to get rid of SingleMessageRecipient in NodeInfo.
- val address = getPartyInfo(mapParty)?.let { network.getAddressOfParty(it) } ?:
- throw IllegalArgumentException("Can't deregister for updates, don't know the party: $mapParty")
- val future = network.sendRequest(NetworkMapService.SUBSCRIPTION_TOPIC, req, address).map {
- if (it.confirmed) Unit else throw NetworkCacheException.DeregistrationFailed()
- }
- _registrationFuture.captureLater(future.map { null })
- return future
- }
-
- fun processUpdatePush(req: NetworkMapService.Update) {
- try {
- val reg = req.wireReg.verified()
- processRegistration(reg)
- } catch (e: SignatureException) {
- throw NodeMapException.InvalidSignature()
- }
- }
-
override val allNodes: List
get() = database.transaction {
getAllInfos(session).map { it.toNodeInfo() }
}
- private fun processRegistration(reg: NodeRegistration) {
- when (reg.type) {
- AddOrRemove.ADD -> addNode(reg.node)
- AddOrRemove.REMOVE -> removeNode(reg.node)
- }
- }
-
- @VisibleForTesting
- override fun runWithoutMapService() {
- _registrationFuture.set(null)
- }
-
// Changes related to NetworkMap redesign
// TODO It will be properly merged into network map cache after services removal.
@@ -288,14 +206,10 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con
logger.info("Loaded node info: $nodeInfo")
val node = nodeInfo.toNodeInfo()
addNode(node)
- _loadDBSuccess = true // This is used in AbstractNode to indicate that node is ready.
} catch (e: Exception) {
logger.warn("Exception parsing network map from the database.", e)
}
}
- if (loadDBSuccess) {
- _registrationFuture.set(null) // Useful only if we don't have NetworkMapService configured so StateMachineManager can start.
- }
}
private fun updateInfoDB(nodeInfo: NodeInfo) {
diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt
index 054d7c5d01..479bbe86da 100644
--- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt
@@ -8,10 +8,7 @@ import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.utilities.UntrustworthyData
-class FlowSessionImpl(
- override val counterparty: Party
-) : FlowSession() {
-
+class FlowSessionImpl(override val counterparty: Party) : FlowSession() {
internal lateinit var stateMachine: FlowStateMachine<*>
internal lateinit var sessionFlow: FlowLogic<*>
@@ -57,5 +54,7 @@ class FlowSessionImpl(
@Suspendable
override fun send(payload: Any) = send(payload, maySkipCheckpoint = false)
+
+ override fun toString() = "Flow session with $counterparty"
}
diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt
index 7fdd7f920c..78dd79cdaa 100644
--- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt
@@ -33,10 +33,7 @@ import net.corda.node.services.api.CheckpointStorage
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.messaging.ReceivedMessage
import net.corda.node.services.messaging.TopicSession
-import net.corda.node.utilities.AffinityExecutor
-import net.corda.node.utilities.CordaPersistence
-import net.corda.node.utilities.bufferUntilDatabaseCommit
-import net.corda.node.utilities.wrapWithDatabaseTransaction
+import net.corda.node.utilities.*
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.withTokenContext
import org.apache.activemq.artemis.utils.ReusableLatch
@@ -46,7 +43,6 @@ import rx.subjects.PublishSubject
import java.io.NotSerializableException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit.SECONDS
import javax.annotation.concurrent.ThreadSafe
@@ -92,7 +88,11 @@ class StateMachineManagerImpl(
private val scheduler = FiberScheduler()
private val mutex = ThreadBox(InnerState())
// This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore.
- private val checkpointCheckerThread = if (serviceHub.configuration.devMode) Executors.newSingleThreadExecutor() else null
+ private val checkpointCheckerThread = if (serviceHub.configuration.devModeOptions?.disableCheckpointChecker != true) {
+ newNamedSingleThreadExecutor("CheckpointChecker")
+ } else {
+ null
+ }
@Volatile private var unrestorableCheckpoints = false
diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt
index c85eb7d4da..f2b88d76b8 100644
--- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt
+++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt
@@ -90,6 +90,7 @@ object BFTSMaRt {
private fun awaitClientConnectionToCluster() {
// TODO: Hopefully we only need to wait for the client's initial connection to the cluster, and this method can be moved to some startup code.
+ // TODO: Investigate ConcurrentModificationException in this method.
while (true) {
val inactive = sessionTable.entries.mapNotNull { if (it.value.channel.isActive) null else it.key }
if (inactive.isEmpty()) break
diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt
index 934cc2cb83..818f0d58bc 100644
--- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt
@@ -13,6 +13,7 @@ import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.SortAttribute
import net.corda.core.messaging.DataFeed
+import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.vault.*
import net.corda.core.schemas.PersistentStateRef
@@ -50,8 +51,7 @@ private fun CriteriaBuilder.executeUpdate(session: Session, configure: Root<*>.(
}
/**
- * Currently, the node vault service is a very simple RDBMS backed implementation. It will change significantly when
- * we add further functionality as the design for the vault and vault service matures.
+ * The vault service handles storage, retrieval and querying of states.
*
* This class needs database transactions to be in-flight during method calls and init, and will throw exceptions if
* this is not the case.
@@ -59,8 +59,12 @@ private fun CriteriaBuilder.executeUpdate(session: Session, configure: Root<*>.(
* TODO: keep an audit trail with time stamps of previously unconsumed states "as of" a particular point in time.
* TODO: have transaction storage do some caching.
*/
-class NodeVaultService(private val clock: Clock, private val keyManagementService: KeyManagementService, private val stateLoader: StateLoader, hibernateConfig: HibernateConfiguration) : SingletonSerializeAsToken(), VaultServiceInternal {
-
+class NodeVaultService(
+ private val clock: Clock,
+ private val keyManagementService: KeyManagementService,
+ private val stateLoader: StateLoader,
+ hibernateConfig: HibernateConfiguration
+) : SingletonSerializeAsToken(), VaultServiceInternal {
private companion object {
val log = loggerFor()
}
@@ -118,7 +122,10 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic
override val updates: Observable>
get() = mutex.locked { _updatesInDbTx }
- override fun notifyAll(txns: Iterable) {
+ override fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable) {
+ if (statesToRecord == StatesToRecord.NONE)
+ return
+
// It'd be easier to just group by type, but then we'd lose ordering.
val regularTxns = mutableListOf()
val notaryChangeTxns = mutableListOf()
@@ -128,30 +135,33 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic
is WireTransaction -> {
regularTxns.add(tx)
if (notaryChangeTxns.isNotEmpty()) {
- notifyNotaryChange(notaryChangeTxns.toList())
+ notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord)
notaryChangeTxns.clear()
}
}
is NotaryChangeWireTransaction -> {
notaryChangeTxns.add(tx)
if (regularTxns.isNotEmpty()) {
- notifyRegular(regularTxns.toList())
+ notifyRegular(regularTxns.toList(), statesToRecord)
regularTxns.clear()
}
}
}
}
- if (regularTxns.isNotEmpty()) notifyRegular(regularTxns.toList())
- if (notaryChangeTxns.isNotEmpty()) notifyNotaryChange(notaryChangeTxns.toList())
+ if (regularTxns.isNotEmpty()) notifyRegular(regularTxns.toList(), statesToRecord)
+ if (notaryChangeTxns.isNotEmpty()) notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord)
}
- private fun notifyRegular(txns: Iterable) {
+ private fun notifyRegular(txns: Iterable, statesToRecord: StatesToRecord) {
fun makeUpdate(tx: WireTransaction): Vault.Update {
val myKeys = keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } })
- val ourNewStates = tx.outputs.
- filter { isRelevant(it.data, myKeys.toSet()) }.
- map { tx.outRef(it.data) }
+
+ val ourNewStates = when (statesToRecord) {
+ StatesToRecord.NONE -> throw AssertionError("Should not reach here")
+ StatesToRecord.ONLY_RELEVANT -> tx.outputs.filter { isRelevant(it.data, myKeys.toSet()) }
+ StatesToRecord.ALL_VISIBLE -> tx.outputs
+ }.map { tx.outRef(it.data) }
// Retrieve all unconsumed states for this transaction's inputs
val consumedStates = loadStates(tx.inputs)
@@ -169,7 +179,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic
processAndNotify(netDelta)
}
- private fun notifyNotaryChange(txns: Iterable) {
+ private fun notifyNotaryChange(txns: Iterable, statesToRecord: StatesToRecord) {
fun makeUpdate(tx: NotaryChangeWireTransaction): Vault.Update {
// We need to resolve the full transaction here because outputs are calculated from inputs
// We also can't do filtering beforehand, since output encumbrance pointers get recalculated based on
@@ -178,7 +188,12 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic
val myKeys = keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } })
val (consumedStateAndRefs, producedStates) = ltx.inputs.
zip(ltx.outputs).
- filter { (_, output) -> isRelevant(output.data, myKeys.toSet()) }.
+ filter { (_, output) ->
+ if (statesToRecord == StatesToRecord.ONLY_RELEVANT)
+ isRelevant(output.data, myKeys.toSet())
+ else
+ true
+ }.
unzip()
val producedStateAndRefs = producedStates.map { ltx.outRef(it.data) }
diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt
index a8c85d105d..77055c6073 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt
@@ -20,14 +20,6 @@ import java.util.concurrent.CopyOnWriteArrayList
*/
const val NODE_DATABASE_PREFIX = "node_"
-/**
- * The maximum supported field-size for hash HEX-encoded outputs (e.g. database fields).
- * This value is enough to support hash functions with outputs up to 512 bits (e.g. SHA3-512), in which
- * case 128 HEX characters are required.
- * 130 was selected instead of 128, to allow for 2 extra characters that will be used as hash-scheme identifiers.
- */
-internal const val MAX_HASH_HEX_SIZE = 130
-
//HikariDataSource implements Closeable which allows CordaPersistence to be Closeable
class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService,
private val createIdentityService: () -> IdentityService, databaseProperties: Properties) : Closeable {
diff --git a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt
new file mode 100644
index 0000000000..c7fd6c0186
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt
@@ -0,0 +1,29 @@
+package net.corda.node.utilities
+
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicInteger
+
+
+/**
+ * Utility class that allows to give threads arbitrary name prefixes when they are created
+ * via an executor. It will use an underlying thread factory to create the actual thread
+ * and then override the thread name with the prefix and an ever increasing number
+ */
+class NamedThreadFactory(private val name:String, private val underlyingFactory: ThreadFactory) : ThreadFactory{
+ val threadNumber = AtomicInteger(1)
+ override fun newThread(runnable: Runnable?): Thread {
+ val thread = underlyingFactory.newThread(runnable)
+ thread.name = name + "-" + threadNumber.getAndIncrement()
+ return thread
+ }
+}
+
+/**
+ * Create a single thread executor with a NamedThreadFactory based on the default thread factory
+ * defined in java.util.concurrent.Executors
+ */
+fun newNamedSingleThreadExecutor(name: String): ExecutorService {
+ return Executors.newSingleThreadExecutor(NamedThreadFactory(name, Executors.defaultThreadFactory()))
+}
diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
index 2ad7697d20..a7ff598d38 100644
--- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
+++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
@@ -33,8 +33,9 @@ import static net.corda.testing.TestConstants.*;
import static net.corda.testing.node.MockServices.*;
import static org.assertj.core.api.Assertions.*;
-public class VaultQueryJavaTests extends TestDependencyInjectionBase {
-
+public class VaultQueryJavaTests {
+ @Rule
+ public SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
private MockServices services;
private MockServices issuerServices;
private VaultService vaultService;
diff --git a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt
index 6135863388..14242aecb3 100644
--- a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt
+++ b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt
@@ -13,7 +13,6 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.internal.cordapp.DummyRPCFlow
-import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
@@ -82,7 +81,7 @@ class CordaServiceTest {
@Before
fun start() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.node.internal","net.corda.finance"))
- notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = true)
+ notaryNode = mockNet.createNotaryNode()
nodeA = mockNet.createNode()
mockNet.startNodes()
}
diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt
index f5e842347e..18ed176857 100644
--- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt
+++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt
@@ -4,7 +4,6 @@ import net.corda.node.services.messaging.Message
import net.corda.node.services.messaging.TopicStringValidator
import net.corda.node.services.messaging.createMessage
import net.corda.testing.node.MockNetwork
-import net.corda.testing.resetTestSerialization
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -23,11 +22,7 @@ class InMemoryMessagingTests {
@After
fun tearDown() {
- if (mockNet.nodes.isNotEmpty()) {
- mockNet.stopNodes()
- } else {
- resetTestSerialization()
- }
+ mockNet.stopNodes()
}
@Test
diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt
index 1beceb0855..ee21427fdd 100644
--- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt
@@ -16,6 +16,7 @@ import net.corda.node.internal.StartedNode
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockNetwork
+import net.corda.testing.node.MockNodeParameters
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
@@ -37,10 +38,10 @@ class NotaryChangeTests {
@Before
fun setUp() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
- oldNotaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
+ oldNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name))
clientNodeA = mockNet.createNode()
clientNodeB = mockNet.createNode()
- newNotaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2"))
+ newNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2")))
mockNet.runNetwork() // Clear network map registration messages
oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!!
newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Dummy Notary 2"))!!
diff --git a/node/src/test/kotlin/net/corda/node/services/RPCUserServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/RPCUserServiceTest.kt
new file mode 100644
index 0000000000..88bdae5579
--- /dev/null
+++ b/node/src/test/kotlin/net/corda/node/services/RPCUserServiceTest.kt
@@ -0,0 +1,20 @@
+package net.corda.node.services
+
+
+import net.corda.nodeapi.User
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.Test
+
+class RPCUserServiceTest {
+
+ @Test
+ fun `Artemis special characters not permitted in RPC usernames`() {
+ assertThatThrownBy { configWithRPCUsername("user.1") }.hasMessageContaining(".")
+ assertThatThrownBy { configWithRPCUsername("user*1") }.hasMessageContaining("*")
+ assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#")
+ }
+
+ private fun configWithRPCUsername(username: String) {
+ RPCUserServiceImpl(listOf(User(username, "password", setOf())))
+ }
+}
\ No newline at end of file
diff --git a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt
index 58f11a9d61..e69de29bb2 100644
--- a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt
@@ -1,47 +0,0 @@
-package net.corda.node.services.config
-
-import net.corda.core.utilities.NetworkHostAndPort
-import net.corda.core.utilities.seconds
-import net.corda.nodeapi.User
-import net.corda.testing.ALICE
-import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
-import org.assertj.core.api.Assertions.assertThatThrownBy
-import org.junit.Test
-import java.net.URL
-import java.nio.file.Paths
-
-class FullNodeConfigurationTest {
- @Test
- fun `Artemis special characters not permitted in RPC usernames`() {
- val testConfiguration = FullNodeConfiguration(
- baseDirectory = Paths.get("."),
- myLegalName = ALICE.name,
- networkMapService = null,
- emailAddress = "",
- keyStorePassword = "cordacadevpass",
- trustStorePassword = "trustpass",
- dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation),
- database = makeTestDatabaseProperties(),
- certificateSigningService = URL("http://localhost"),
- rpcUsers = emptyList(),
- verifierType = VerifierType.InMemory,
- useHTTPS = false,
- p2pAddress = NetworkHostAndPort("localhost", 0),
- rpcAddress = NetworkHostAndPort("localhost", 1),
- messagingServerAddress = null,
- notary = null,
- certificateChainCheckPolicies = emptyList(),
- devMode = true,
- activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)),
- additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis(),
- relay = null)
-
- fun configWithRPCUsername(username: String) {
- testConfiguration.copy(rpcUsers = listOf(User(username, "pass", emptySet())))
- }
- assertThatThrownBy { configWithRPCUsername("user.1") }.hasMessageContaining(".")
- assertThatThrownBy { configWithRPCUsername("user*1") }.hasMessageContaining("*")
- assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#")
- }
-}
diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
new file mode 100644
index 0000000000..0976f53bad
--- /dev/null
+++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
@@ -0,0 +1,56 @@
+package net.corda.node.services.config
+
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.testing.ALICE
+import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
+import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.Test
+import java.net.URL
+import java.nio.file.Paths
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class NodeConfigurationImplTest {
+ @Test
+ fun `can't have dev mode options if not in dev mode`() {
+ val debugOptions = DevModeOptions()
+ configDebugOptions(true, debugOptions)
+ configDebugOptions(true, null)
+ assertThatThrownBy { configDebugOptions(false, debugOptions) }.hasMessageMatching("Cannot use devModeOptions outside of dev mode")
+ configDebugOptions(false, null)
+ }
+
+ @Test
+ fun `check devModeOptions flag helper`() {
+ assertFalse { configDebugOptions(true, null).devModeOptions?.disableCheckpointChecker == true }
+ assertFalse { configDebugOptions(true, DevModeOptions()).devModeOptions?.disableCheckpointChecker == true }
+ assertFalse { configDebugOptions(true, DevModeOptions(false)).devModeOptions?.disableCheckpointChecker == true }
+ assertTrue { configDebugOptions(true, DevModeOptions(true)).devModeOptions?.disableCheckpointChecker == true }
+ }
+
+ private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?) : NodeConfiguration {
+ return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
+ }
+
+ private val testConfiguration = NodeConfigurationImpl(
+ baseDirectory = Paths.get("."),
+ myLegalName = ALICE.name,
+ emailAddress = "",
+ keyStorePassword = "cordacadevpass",
+ trustStorePassword = "trustpass",
+ dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation),
+ database = makeTestDatabaseProperties(),
+ certificateSigningService = URL("http://localhost"),
+ rpcUsers = emptyList(),
+ verifierType = VerifierType.InMemory,
+ useHTTPS = false,
+ p2pAddress = NetworkHostAndPort("localhost", 0),
+ rpcAddress = NetworkHostAndPort("localhost", 1),
+ messagingServerAddress = null,
+ notary = null,
+ certificateChainCheckPolicies = emptyList(),
+ devMode = true,
+ activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)),
+ relay = null)
+}
diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt
index 446976faf2..9bce320c38 100644
--- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt
@@ -12,6 +12,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
+import net.corda.core.node.StatesToRecord
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
@@ -41,6 +42,7 @@ import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.nio.file.Paths
import java.security.PublicKey
@@ -56,6 +58,9 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
private val myInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(DUMMY_IDENTITY_1), 1, serial = 1L)
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
private val realClock: Clock = Clock.systemUTC()
private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone)
private val testClock = TestClock(stoppedClock)
@@ -85,7 +90,6 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
@Before
fun setup() {
- initialiseTestSerialization()
countDown = CountDownLatch(1)
smmHasRemovedAllFlows = CountDownLatch(1)
calls = 0
@@ -107,11 +111,12 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
doReturn(myInfo).whenever(it).myInfo
doReturn(kms).whenever(it).keyManagementService
doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), MockAttachmentStorage())).whenever(it).cordappProvider
- doCallRealMethod().whenever(it).recordTransactions(any())
- doCallRealMethod().whenever(it).recordTransactions(any(), any())
- doCallRealMethod().whenever(it).recordTransactions(any(), any>())
+ doCallRealMethod().whenever(it).recordTransactions(any(), any())
+ doCallRealMethod().whenever(it).recordTransactions(any>())
+ doCallRealMethod().whenever(it).recordTransactions(any(), anyVararg())
doReturn(NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig)).whenever(it).vaultService
doReturn(this@NodeSchedulerServiceTest).whenever(it).testReference
+
}
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
mockSMM = StateMachineManagerImpl(services, DBCheckpointStorage(), smmExecutor, database)
@@ -135,11 +140,11 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
smmExecutor.shutdown()
smmExecutor.awaitTermination(60, TimeUnit.SECONDS)
database.close()
- resetTestSerialization()
}
// Ignore IntelliJ when it says these properties can be private, if they are we cannot serialise them
// in AMQP.
+ @Suppress("MemberVisibilityCanPrivate")
class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, val myIdentity: Party) : LinearState, SchedulableState {
override val participants: List
get() = listOf(myIdentity)
diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt
index 9999b927e0..db4b9dde89 100644
--- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt
@@ -16,7 +16,6 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.node.services.statemachine.StateMachineManager
-import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
@@ -95,7 +94,7 @@ class ScheduledFlowTests {
@Before
fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts"))
- notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
+ notaryNode = mockNet.createNotaryNode()
val a = mockNet.createUnstartedNode()
val b = mockNet.createUnstartedNode()
diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt
index b7fdbd40eb..b18b7a1766 100644
--- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt
@@ -39,7 +39,10 @@ import kotlin.test.assertEquals
import kotlin.test.assertNull
//TODO This needs to be merged into P2PMessagingTest as that creates a more realistic environment
-class ArtemisMessagingTests : TestDependencyInjectionBase() {
+class ArtemisMessagingTests {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
@Rule
@JvmField
val temporaryFolder = TemporaryFolder()
diff --git a/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt
index 058762bb7f..557877f6c7 100644
--- a/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt
@@ -10,6 +10,7 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities
+import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.TestDependencyInjectionBase
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x500.X500Name
@@ -23,6 +24,7 @@ import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.InputStream
@@ -37,7 +39,10 @@ import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
import kotlin.test.assertEquals
-class HTTPNetworkMapClientTest : TestDependencyInjectionBase() {
+class HTTPNetworkMapClientTest {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
private lateinit var server: Server
private lateinit var networkMapClient: NetworkMapClient
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt
index f71f266388..489ad49198 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt
@@ -8,13 +8,14 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.LogHelper
-import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
+import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
internal fun CheckpointStorage.checkpoints(): List {
@@ -26,7 +27,10 @@ internal fun CheckpointStorage.checkpoints(): List {
return checkpoints
}
-class DBCheckpointStorageTests : TestDependencyInjectionBase() {
+class DBCheckpointStorageTests {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
lateinit var checkpointStorage: DBCheckpointStorage
lateinit var database: CordaPersistence
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt
index 160ea87bf3..28026dd049 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt
@@ -5,13 +5,13 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
+import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.VaultService
import net.corda.core.toFuture
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.schema.HibernateObserver
-import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.CordaPersistence
@@ -24,11 +24,15 @@ import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
-class DBTransactionStorageTests : TestDependencyInjectionBase() {
+class DBTransactionStorageTests {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
lateinit var database: CordaPersistence
lateinit var transactionStorage: DBTransactionStorage
lateinit var services: MockServices
@@ -40,7 +44,6 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() {
val dataSourceProps = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), ::makeTestIdentityService)
database.transaction {
-
services = object : MockServices(BOB_KEY) {
override val vaultService: VaultServiceInternal
get() {
@@ -54,7 +57,7 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
- vaultService.notifyAll(txs.map { it.tx })
+ vaultService.notifyAll(StatesToRecord.ONLY_RELEVANT, txs.map { it.tx })
}
}
}
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt
index 9f6fe30d11..73f7570e4d 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt
@@ -5,14 +5,15 @@ import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.SecureHash
+import net.corda.core.node.StatesToRecord
import net.corda.core.utilities.toBase58String
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.schemas.CommonSchemaV1
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentStateRef
-import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize
+import net.corda.core.serialization.SerializationDefaults
import net.corda.core.transactions.SignedTransaction
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
@@ -43,10 +44,7 @@ import net.corda.testing.schemas.DummyLinearStateSchemaV2
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.hibernate.SessionFactory
-import org.junit.After
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Test
+import org.junit.*
import java.math.BigDecimal
import java.time.Instant
import java.util.*
@@ -54,8 +52,10 @@ import javax.persistence.EntityManager
import javax.persistence.Tuple
import javax.persistence.criteria.CriteriaBuilder
-class HibernateConfigurationTest : TestDependencyInjectionBase() {
-
+class HibernateConfigurationTest {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
lateinit var services: MockServices
lateinit var issuerServices: MockServices
lateinit var database: CordaPersistence
@@ -82,12 +82,12 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
hibernateConfig = database.hibernateConfig
services = object : MockServices(cordappPackages, BOB_KEY, BOC_KEY, DUMMY_NOTARY_KEY) {
override val vaultService = makeVaultService(database.hibernateConfig)
- override fun recordTransactions(notifyVault: Boolean, txs: Iterable) {
+ override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
- vaultService.notifyAll(txs.map { it.tx })
+ vaultService.notifyAll(statesToRecord, txs.map { it.tx })
}
override fun jdbcSession() = database.createSession()
diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt
index 044c953422..42229d2f4c 100644
--- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt
@@ -146,16 +146,6 @@ class FlowFrameworkTests {
assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello")
}
- @Test
- fun `flow added before network map does run after init`() {
- val charlieNode = mockNet.createNode() //create vanilla node
- val flow = NoOpFlow()
- charlieNode.services.startFlow(flow)
- assertEquals(false, flow.flowStarted) // Not started yet as no network activity has been allowed yet
- mockNet.runNetwork() // Allow network map messages to flow
- assertEquals(true, flow.flowStarted) // Now we should have run the flow
- }
-
@Test
fun `flow loaded from checkpoint will respond to messages from before start`() {
aliceNode.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) }
diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt
index ce79064a79..b2a34d127e 100644
--- a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt
@@ -18,14 +18,18 @@ import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.util.concurrent.CompletableFuture
import kotlin.test.assertEquals
import kotlin.test.assertTrue
-class DistributedImmutableMapTests : TestDependencyInjectionBase() {
+class DistributedImmutableMapTests {
data class Member(val client: CopycatClient, val server: CopycatServer)
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
lateinit var cluster: List
lateinit var transaction: DatabaseTransaction
private val databases: MutableList = mutableListOf()
diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt
index 2510497167..05bf4ecdca 100644
--- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt
@@ -37,7 +37,7 @@ class NotaryServiceTests {
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
- val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = false)
+ val notaryNode = mockNet.createNotaryNode(validating = false)
aliceServices = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)).services
mockNet.runNetwork() // Clear network map registration messages
notaryServices = notaryNode.services
diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt
index 088a14e18f..0bb0bfeb2c 100644
--- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt
@@ -10,11 +10,15 @@ import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
-class PersistentUniquenessProviderTests : TestDependencyInjectionBase() {
+class PersistentUniquenessProviderTests {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
val identity = MEGA_CORP
val txID = SecureHash.randomSHA256()
diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt
index 1a1c74ea07..4fe3f68a20 100644
--- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt
@@ -37,7 +37,7 @@ class ValidatingNotaryServiceTests {
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
- val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
+ val notaryNode = mockNet.createNotaryNode()
val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
mockNet.runNetwork() // Clear network map registration messages
notaryServices = notaryNode.services
diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt
index 46802e8a7f..6431abeed3 100644
--- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt
@@ -1,16 +1,15 @@
package net.corda.node.services.vault
import co.paralleluniverse.fibers.Suspendable
-import net.corda.core.contracts.Amount
-import net.corda.core.contracts.Issued
-import net.corda.core.contracts.StateAndRef
-import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.internal.packageName
+import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.*
+import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.QueryCriteria.*
import net.corda.core.transactions.NotaryChangeWireTransaction
@@ -30,11 +29,11 @@ import net.corda.node.utilities.CordaPersistence
import net.corda.testing.*
import net.corda.testing.contracts.fillWithSomeTestCash
import net.corda.testing.node.MockServices
-import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import rx.observers.TestSubscriber
import java.math.BigDecimal
@@ -45,11 +44,14 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
-class NodeVaultServiceTest : TestDependencyInjectionBase() {
+class NodeVaultServiceTest {
companion object {
private val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
lateinit var services: MockServices
private lateinit var issuerServices: MockServices
val vaultService get() = services.vaultService as NodeVaultService
@@ -58,8 +60,10 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Before
fun setUp() {
LogHelper.setLevel(NodeVaultService::class)
- val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(BOC_KEY, DUMMY_CASH_ISSUER_KEY),
- cordappPackages = cordappPackages)
+ val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
+ keys = listOf(BOC_KEY, DUMMY_CASH_ISSUER_KEY),
+ cordappPackages = cordappPackages
+ )
database = databaseAndServices.first
services = databaseAndServices.second
issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_KEY, BOC_KEY)
@@ -102,10 +106,10 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val originalVault = vaultService
val services2 = object : MockServices() {
override val vaultService: NodeVaultService get() = originalVault
- override fun recordTransactions(notifyVault: Boolean, txs: Iterable) {
+ override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
- vaultService.notify(stx.tx)
+ vaultService.notify(statesToRecord, stx.tx)
}
}
}
@@ -512,14 +516,14 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
}.toWireTransaction(services)
val cashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0))
- database.transaction { service.notify(issueTx) }
+ database.transaction { service.notify(StatesToRecord.ONLY_RELEVANT, issueTx) }
val expectedIssueUpdate = Vault.Update(emptySet(), setOf(cashState), null)
database.transaction {
val moveTx = TransactionBuilder(services.myInfo.chooseIdentity()).apply {
Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity)
}.toWireTransaction(services)
- service.notify(moveTx)
+ service.notify(StatesToRecord.ONLY_RELEVANT, moveTx)
}
val expectedMoveUpdate = Vault.Update(setOf(cashState), emptySet(), null)
@@ -556,7 +560,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0))
database.transaction {
- service.notifyAll(listOf(issueStx.tx, changeNotaryTx))
+ service.notifyAll(StatesToRecord.ONLY_RELEVANT, listOf(issueStx.tx, changeNotaryTx))
}
// Move cash
@@ -567,7 +571,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
}
database.transaction {
- service.notify(moveTx)
+ service.notify(StatesToRecord.ONLY_RELEVANT, moveTx)
}
val expectedIssueUpdate = Vault.Update(emptySet(), setOf(initialCashState), null)
@@ -577,4 +581,32 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val observedUpdates = vaultSubscriber.onNextEvents
assertEquals(observedUpdates, listOf(expectedIssueUpdate, expectedNotaryChangeUpdate, expectedMoveUpdate))
}
+
+ @Test
+ fun observerMode() {
+ fun countCash(): Long {
+ return database.transaction {
+ vaultService.queryBy(Cash.State::class.java, QueryCriteria.VaultQueryCriteria(), PageSpecification(1)).totalStatesAvailable
+ }
+ }
+ val currentCashStates = countCash()
+
+ // Send some minimalist dummy transaction.
+ val txb = TransactionBuilder(DUMMY_NOTARY)
+ txb.addOutputState(Cash.State(MEGA_CORP.ref(0), 100.DOLLARS, MINI_CORP), Cash::class.java.name)
+ txb.addCommand(Cash.Commands.Move(), MEGA_CORP_PUBKEY)
+ val wtx = txb.toWireTransaction(services)
+ database.transaction {
+ vaultService.notify(StatesToRecord.ONLY_RELEVANT, wtx)
+ }
+
+ // Check that it was ignored as irrelevant.
+ assertEquals(currentCashStates, countCash())
+
+ // Now try again and check it was accepted.
+ database.transaction {
+ vaultService.notify(StatesToRecord.ALL_VISIBLE, wtx)
+ }
+ assertEquals(currentCashStates + 1, countCash())
+ }
}
diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
index a71f84f6ea..a5f1e44808 100644
--- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
@@ -46,7 +46,10 @@ import java.time.ZoneOffset
import java.time.temporal.ChronoUnit
import java.util.*
-class VaultQueryTests : TestDependencyInjectionBase() {
+class VaultQueryTests {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
private val cordappPackages = setOf(
"net.corda.testing.contracts", "net.corda.finance.contracts",
CashSchemaV1::class.packageName, CommercialPaperSchemaV1::class.packageName, DummyLinearStateSchemaV1::class.packageName).toMutableList()
diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt
index feb6530062..dfc4db15e1 100644
--- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt
@@ -26,6 +26,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.util.*
import java.util.concurrent.CountDownLatch
@@ -34,11 +35,14 @@ import kotlin.test.assertEquals
// TODO: Move this to the cash contract tests once mock services are further split up.
-class VaultWithCashTest : TestDependencyInjectionBase() {
+class VaultWithCashTest {
companion object {
private val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
lateinit var services: MockServices
lateinit var issuerServices: MockServices
val vaultService: VaultService get() = services.vaultService
diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt
index dade7aa141..ae341985d2 100644
--- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt
+++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt
@@ -216,8 +216,7 @@ class CommercialPaperTestsGeneric {
// @Test
@Ignore
- fun `issue move and then redeem`() {
- initialiseTestSerialization()
+ fun `issue move and then redeem`() = withTestSerialization{
val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY))
val databaseAlice = aliceDatabaseAndServices.first
aliceServices = aliceDatabaseAndServices.second
@@ -297,6 +296,5 @@ class CommercialPaperTestsGeneric {
validRedemption.toLedgerTransaction(aliceServices).verify()
// soft lock not released after success either!!! (as transaction not recorded)
}
- resetTestSerialization()
}
}
diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt
index 8a67039948..9d27df3756 100644
--- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt
+++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt
@@ -80,7 +80,7 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount,
}
-class CashTests : TestDependencyInjectionBase() {
+class CashTests {
private val defaultRef = OpaqueBytes(ByteArray(1, { 1 }))
private val defaultIssuer = MEGA_CORP.ref(defaultRef)
private val inState = Cash.State(
@@ -102,7 +102,7 @@ class CashTests : TestDependencyInjectionBase() {
private lateinit var vaultStatesUnconsumed: List>
@Before
- fun setUp() {
+ fun setUp() = withTestSerialization {
LogHelper.setLevel(NodeVaultService::class)
megaCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset"), MEGA_CORP_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices(cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset"), keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY))
@@ -123,7 +123,6 @@ class CashTests : TestDependencyInjectionBase() {
database.transaction {
vaultStatesUnconsumed = miniCorpServices.vaultService.queryBy().states
}
- resetTestSerialization()
}
@After
@@ -205,8 +204,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)
@@ -221,8 +219,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 {
@@ -290,8 +287,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)
@@ -302,6 +298,7 @@ class CashTests : TestDependencyInjectionBase() {
ptx = TransactionBuilder(DUMMY_NOTARY)
ptx.addInputState(tx.tx.outRef(0))
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY)
+ Unit
}
@Test
@@ -566,8 +563,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)
@@ -582,8 +578,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
@@ -600,53 +595,52 @@ class CashTests : TestDependencyInjectionBase() {
* Try exiting a currency we don't have.
*/
@Test
- fun generateAbsentExit() {
- initialiseTestSerialization()
+ fun generateAbsentExit() = withTestSerialization {
assertFailsWith { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 1) }
+ Unit
}
/**
* Try exiting with a reference mis-match.
*/
@Test
- fun generateInvalidReferenceExit() {
- initialiseTestSerialization()
+ fun generateInvalidReferenceExit() = withTestSerialization {
assertFailsWith { 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 { makeExit(miniCorpServices, 1000.DOLLARS, MEGA_CORP, 1) }
+ Unit
}
/**
* Try exiting for an owner with no states
*/
@Test
- fun generateOwnerWithNoStatesExit() {
- initialiseTestSerialization()
+ fun generateOwnerWithNoStatesExit() = withTestSerialization {
assertFailsWith { makeExit(miniCorpServices, 100.POUNDS, CHARLIE, 1) }
+ Unit
}
/**
* Try exiting when vault is empty
*/
@Test
- fun generateExitWithEmptyVault() {
- initialiseTestSerialization()
+ fun generateExitWithEmptyVault() = withTestSerialization {
assertFailsWith {
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)
@@ -660,8 +654,7 @@ class CashTests : TestDependencyInjectionBase() {
}
@Test
- fun generateSimpleSpendWithParties() {
- initialiseTestSerialization()
+ fun generateSimpleSpendWithParties() = withTestSerialization {
database.transaction {
val tx = TransactionBuilder(DUMMY_NOTARY)
@@ -672,8 +665,7 @@ class CashTests : TestDependencyInjectionBase() {
}
@Test
- fun generateSimpleSpendWithChange() {
- initialiseTestSerialization()
+ fun generateSimpleSpendWithChange() = withTestSerialization {
val wtx =
database.transaction {
makeSpend(10.DOLLARS, THEIR_IDENTITY_1)
@@ -698,8 +690,7 @@ class CashTests : TestDependencyInjectionBase() {
}
@Test
- fun generateSpendWithTwoInputs() {
- initialiseTestSerialization()
+ fun generateSpendWithTwoInputs() = withTestSerialization {
val wtx =
database.transaction {
makeSpend(500.DOLLARS, THEIR_IDENTITY_1)
@@ -715,8 +706,7 @@ class CashTests : TestDependencyInjectionBase() {
}
@Test
- fun generateSpendMixedDeposits() {
- initialiseTestSerialization()
+ fun generateSpendMixedDeposits() = withTestSerialization {
val wtx =
database.transaction {
val wtx = makeSpend(580.DOLLARS, THEIR_IDENTITY_1)
@@ -737,8 +727,7 @@ class CashTests : TestDependencyInjectionBase() {
}
@Test
- fun generateSpendInsufficientBalance() {
- initialiseTestSerialization()
+ fun generateSpendInsufficientBalance() = withTestSerialization {
database.transaction {
val e: InsufficientBalanceException = assertFailsWith("balance") {
@@ -750,6 +739,7 @@ class CashTests : TestDependencyInjectionBase() {
makeSpend(81.SWISS_FRANCS, THEIR_IDENTITY_1)
}
}
+ Unit
}
/**
@@ -876,8 +866,7 @@ class CashTests : TestDependencyInjectionBase() {
}
@Test
- fun multiSpend() {
- initialiseTestSerialization()
+ fun multiSpend() = withTestSerialization {
val tx = TransactionBuilder(DUMMY_NOTARY)
database.transaction {
val payments = listOf(
diff --git a/samples/attachment-demo/README.md b/samples/attachment-demo/README.md
index effd75fc67..4aa02651f2 100644
--- a/samples/attachment-demo/README.md
+++ b/samples/attachment-demo/README.md
@@ -1,14 +1,23 @@
-# Attachment Demo
+# Attachment Demo
-This code demonstrates sending a transaction with an attachment from one to node to another, and the receiving node accessing the attachment.
+This demo brings up three nodes, and sends a transaction containing an attachment from one to the other.
-Please see the either the [online documentation](https://docs.corda.net/running-the-demos.html#attachment-demo) for more info on the attachment demo, or the [included offline version](../../docs/build/html/running-the-demos.html#attachment-demo).
+To run from the command line in Unix:
-From the root directory of the repository, run the following commands (on mac / unix, replace `gradle` with `./gradlew`)
+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
- gradle samples:attachment-demo:deployNodes
+To run from the command line in Windows:
- ./samples/attachment-demo/build/nodes/runnodes
-
- gradle samples:attachment-demo:runRecipient # (in one window)
- gradle samples:attachment-demo:runSender # (in another window)
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/samples/bank-of-corda-demo/README.md b/samples/bank-of-corda-demo/README.md
index 54d30376fe..6effb22a99 100644
--- a/samples/bank-of-corda-demo/README.md
+++ b/samples/bank-of-corda-demo/README.md
@@ -1,58 +1,53 @@
-# Bank of Corda demo
-Please see docs/build/html/running-the-demos.html
+Bank Of Corda demo
+------------------
-This program simulates the role of an asset issuing authority (eg. central bank for cash) by accepting requests
-from third parties to issue some quantity of an asset and transfer that ownership to the requester.
-The issuing authority accepts requests via the [IssuerFlow] flow, self-issues the asset and transfers
-ownership to the issue requester. Notarisation and signing form part of the flow.
+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).
-The requesting party can be a CorDapp (running locally or remotely to the Bank of Corda node), a remote RPC client or
-a Web Client.
+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.
-## Prerequisites
+.. note:: The Bank of Corda is somewhat like a "Bitcoin faucet" that dispenses free bitcoins to developers for
+ testing and experimentation purposes.
-You will need to have [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
-installed and available on your path.
+To run from the command line in Unix:
-## Getting Started
+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
-1. Launch the Bank of Corda node (and associated Notary) by running:
-[BankOfCordaDriver] --role ISSUER
-(to validate your Node is running you can try navigating to this sample link: http://localhost:10005/api/bank/date)
+To run from the command line in Windows:
-Each of the following commands will launch a separate Node called Big Corporation which will become the owner
-of some Cash following an issue request:
+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
-2. Run the Bank of Corda Client driver (to simulate a web issue requester) by running:
-[BankOfCordaDriver] --role ISSUE_CASH_WEB
-This demonstrates a remote application acting on behalf of the Big Corporation and communicating directly with the
-Bank of Corda node via HTTP to request issuance of some cash.
+To verify that the Bank of Corda node is alive and running, navigate to the following URL:
+http://localhost:10007/api/bank/date
-3. Run the Bank of Corda Client driver (to simulate an RPC issue requester) by running:
-[BankOfCordaDriver] --role ISSUE_CASH_RPC
-Similar to 3 above, but using RPC as the remote communications mechanism.
+In the window you run the command you should see (in case of Web, RPC is similar):
-## Developer notes
+- Requesting Cash via Web ...
+- Successfully processed Cash Issue request
-Testing of the Bank of Corda application is demonstrated at two levels:
-1. Unit testing the flow uses the [LedgerDSL] and [MockServices]. Please see [IssuerFlowTest]
- The IssuerFlow is one of several reusable flows defined in the finance package.
-2. Integration testing via RPC and HTTP uses the [Driver] DSL to launch standalone node instances
+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.
-Security
-The RPC API requires a client to pass in user credentials:
- client.start("bankUser","test")
-which are validated on the Bank of Corda node against those configured at node startup:
- User("bankUser", "test", permissions = setOf(startFlowPermission()))
- startNode(BOC.name, rpcUsers = listOf(user))
+Launch the Explorer application to visualize the issuance and transfer of cash for each node:
-Notary
-We are using a [SimpleNotaryService] in this example, but could easily switch to a [ValidatingNotaryService]
+ ``./gradlew tools:explorer:run`` (on Unix) or ``gradlew tools:explorer:run`` (on Windows)
-## Integration with other Demos and Tools
+Using the following login details:
-The Bank of Corda issuer node concept has been integrated into the Explorer tool (simulation nodes) and Trader Demo.
+- 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
-## Further Reading
-
-Tutorials and developer docs for Cordapps and Corda are [here](https://docs.corda.net/).
\ No newline at end of file
+See https://docs.corda.net/node-explorer.html for further details on usage.
\ No newline at end of file
diff --git a/samples/irs-demo/README.md b/samples/irs-demo/README.md
index b56f4b5321..b6576f192a 100644
--- a/samples/irs-demo/README.md
+++ b/samples/irs-demo/README.md
@@ -1,7 +1,36 @@
# IRS Demo
-This code demonstrates an Interest Rate Swap agreement between two banks, confirming the transaction utilising the services
-of a notary and then retrieving and applying interest rates from an oracle service.
+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.
+Functionality is split into two parts - CordApp which provides actual distributed ledger backend and Spring Boot
+webapp which provides REST API and web frontend. Application communicate using Corda RPC protocol.
-Please see the either the [online documentation](https://docs.corda.net/running-the-demos.html#irs-demo) for more info on the attachment demo, or the [included offline version](../../docs/build/html/running-the-demos.html#irs-demo).
\ No newline at end of file
+To run from the command line in Unix:
+1. Run ``./gradlew samples:irs-demo:cordapp:deployNodes`` to install configs and a command line tool under
+ ``samples/irs-demo/build``
+2. Run ``./gradlew samples:irs-demo:web:deployWebapps`` to install configs and tools for running webservers
+3. Move to the ``samples/irs-demo/`` directory
+4. Run ``./cordapp/build/nodes/runnodes`` to open up three new terminals with the three nodes (you may have to install xterm)
+5. Run ``./web/build/webapps/runwebapps`` to open three more terminals for associated webserver
+
+To run from the command line in Windows:
+
+1. Run ``gradlew.bat samples:irs-demo:cordapp:deployNodes`` to install configs and a command line tool under
+ ``samples\irs-demo\build``
+2. Run ``gradlew.bat samples:irs-demo:web:deployWebapps`` to install configs and tools for running webservers
+3. Run ``cd samples\irs-demo`` to change current working directory
+4. Run ``cordapp\build\nodes\runnodes`` to open up several 3 terminals for each nodes
+5. Run ``web\build\webapps\webapps`` to open up several 3 terminals for each nodes' webservers
+
+This demo also has a web app. To use this, run nodes and then navigate to http://localhost:10007/ and
+http://localhost:10010/ 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 here: https://github.com/corda/oracle-example.
\ No newline at end of file
diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle
index bcb20c214a..5b5b51ecf9 100644
--- a/samples/irs-demo/build.gradle
+++ b/samples/irs-demo/build.gradle
@@ -1,11 +1,26 @@
+buildscript {
+ ext {
+ springBootVersion = '1.5.7.RELEASE'
+ }
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+ }
+}
+
+// Spring Boot plugin adds a numerous hardcoded dependencies in the version much lower then Corda expects
+// causing the problems in runtime. Those can be changed by manipulating above properties
+// See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property
+ext['artemis.version'] = "$artemis_version"
+ext['hibernate.version'] = "$hibernate_version"
+
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'idea'
+apply plugin: 'org.springframework.boot'
apply plugin: 'net.corda.plugins.quasar-utils'
-apply plugin: 'net.corda.plugins.publish-utils'
-apply plugin: 'net.corda.plugins.cordapp'
-apply plugin: 'net.corda.plugins.cordformation'
-apply plugin: 'maven-publish'
apply plugin: 'application'
mainClassName = 'net.corda.irs.IRSDemo'
@@ -23,59 +38,25 @@ sourceSets {
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
+ demoArtifacts.extendsFrom testRuntime
}
dependencies {
- compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
-
- // The irs demo CorDapp depends upon Cash CorDapp features
- cordapp project(':finance')
-
- // Corda integration dependencies
- cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
- cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
- cordaCompile project(':core')
- cordaCompile project(':webserver')
-
- // Javax is required for webapis
- compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
-
- // Cordapp dependencies
- // Specify your cordapp's dependencies below, including dependent cordapps
- compile "com.squareup.okhttp3:okhttp:$okhttp_version"
+ compile group: 'commons-io', name: 'commons-io', version: '2.5'
+ compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts")
+ compile project(":samples:irs-demo:web")
+ compile('org.springframework.boot:spring-boot-starter-web') {
+ exclude module: "spring-boot-starter-logging"
+ exclude module: "logback-classic"
+ }
testCompile project(':node-driver')
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
}
-task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
- directory "./build/nodes"
- node {
- name "O=Notary Service,L=Zurich,C=CH"
- notary = [validating : true]
- p2pPort 10002
- rpcPort 10003
- webPort 10004
- cordapps = ["$project.group:finance:$corda_release_version"]
- useTestClock true
- }
- node {
- name "O=Bank A,L=London,C=GB"
- p2pPort 10005
- rpcPort 10006
- webPort 10007
- cordapps = ["$project.group:finance:$corda_release_version"]
- useTestClock true
- }
- node {
- name "O=Bank B,L=New York,C=US"
- p2pPort 10008
- rpcPort 10009
- webPort 10010
- cordapps = ["$project.group:finance:$corda_release_version"]
- useTestClock true
- }
+bootRepackage {
+ enabled = false
}
task integrationTest(type: Test, dependsOn: []) {
@@ -83,41 +64,9 @@ task integrationTest(type: Test, dependsOn: []) {
classpath = sourceSets.integrationTest.runtimeClasspath
}
-// This fixes the "line too long" error when running this demo with windows CLI
-// TODO: Automatically apply to all projects via a plugin
-tasks.withType(CreateStartScripts).each { task ->
- task.doLast {
- String text = task.windowsScript.text
- // Replaces the per file classpath (which are all jars in "lib") with a wildcard on lib
- text = text.replaceFirst(/(set CLASSPATH=%APP_HOME%\\lib\\).*/, { "${it[1]}*" })
- task.windowsScript.write text
- }
-}
-
idea {
module {
downloadJavadoc = true // defaults to false
downloadSources = true
}
-}
-
-publishing {
- publications {
- jarAndSources(MavenPublication) {
- from components.java
- artifactId 'irsdemo'
-
- artifact sourceJar
- artifact javadocJar
- }
- }
-}
-
-jar {
- from sourceSets.test.output
- manifest {
- attributes(
- 'Automatic-Module-Name': 'net.corda.samples.demos.irs'
- )
- }
-}
+}
\ No newline at end of file
diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle
new file mode 100644
index 0000000000..74dfa92b45
--- /dev/null
+++ b/samples/irs-demo/cordapp/build.gradle
@@ -0,0 +1,115 @@
+apply plugin: 'java'
+apply plugin: 'kotlin'
+apply plugin: 'idea'
+apply plugin: 'net.corda.plugins.quasar-utils'
+apply plugin: 'net.corda.plugins.publish-utils'
+apply plugin: 'net.corda.plugins.cordformation'
+apply plugin: 'net.corda.plugins.cordapp'
+apply plugin: 'maven-publish'
+apply plugin: 'application'
+
+mainClassName = 'net.corda.irs.IRSDemo'
+
+sourceSets {
+ integrationTest {
+ kotlin {
+ compileClasspath += main.output + test.output
+ runtimeClasspath += main.output + test.output
+ srcDir file('src/integration-test/kotlin')
+ }
+ }
+}
+
+configurations {
+ integrationTestCompile.extendsFrom testCompile
+ integrationTestRuntime.extendsFrom testRuntime
+ demoArtifacts.extendsFrom integrationTestRuntime
+}
+
+dependencies {
+ // The irs demo CorDapp depends upon Cash CorDapp features
+ cordapp project(':finance')
+
+ // Corda integration dependencies
+ cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
+ cordaCompile project(':core')
+
+ // Cordapp dependencies
+ // Specify your cordapp's dependencies below, including dependent cordapps
+ compile group: 'commons-io', name: 'commons-io', version: '2.5'
+
+ testCompile project(':node-driver')
+ testCompile "junit:junit:$junit_version"
+ testCompile "org.assertj:assertj-core:${assertj_version}"
+}
+
+task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
+
+ ext.rpcUsers = [
+ ['username' : "user",
+ 'password' : "password",
+ 'permissions' : [
+ "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester",
+ "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast",
+ "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow"
+ ]]
+ ]
+
+ directory "./build/nodes"
+ node {
+ name "O=Notary Service,L=Zurich,C=CH"
+ notary = [validating : true]
+ p2pPort 10002
+ rpcPort 10003
+ cordapps = ["${project.group}:finance:$corda_release_version"]
+ rpcUsers = ext.rpcUsers
+ useTestClock true
+ }
+ node {
+ name "O=Bank A,L=London,C=GB"
+ p2pPort 10005
+ rpcPort 10006
+ cordapps = ["${project.group}:finance:$corda_release_version"]
+ rpcUsers = ext.rpcUsers
+ useTestClock true
+ }
+ node {
+ name "O=Bank B,L=New York,C=US"
+ p2pPort 10008
+ rpcPort 10009
+ cordapps = ["${project.group}:finance:$corda_release_version"]
+ rpcUsers = ext.rpcUsers
+ useTestClock true
+ }
+}
+
+task integrationTest(type: Test, dependsOn: []) {
+ testClassesDirs = sourceSets.integrationTest.output.classesDirs
+ classpath = sourceSets.integrationTest.runtimeClasspath
+}
+
+// This fixes the "line too long" error when running this demo with windows CLI
+// TODO: Automatically apply to all projects via a plugin
+tasks.withType(CreateStartScripts).each { task ->
+ task.doLast {
+ String text = task.windowsScript.text
+ // Replaces the per file classpath (which are all jars in "lib") with a wildcard on lib
+ text = text.replaceFirst(/(set CLASSPATH=%APP_HOME%\\lib\\).*/, { "${it[1]}*" })
+ task.windowsScript.write text
+ }
+}
+
+idea {
+ module {
+ downloadJavadoc = true // defaults to false
+ downloadSources = true
+ }
+}
+
+jar {
+ from sourceSets.test.output
+}
+
+artifacts {
+ demoArtifacts jar
+}
\ No newline at end of file
diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
similarity index 100%
rename from samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt
similarity index 100%
rename from samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt
rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt
diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSExport.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSExport.kt
similarity index 100%
rename from samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSExport.kt
rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSExport.kt
diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt
similarity index 100%
rename from samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt
rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt
diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt
similarity index 61%
rename from samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt
rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt
index 3500e75eb4..9da48655a9 100644
--- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt
+++ b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt
@@ -2,8 +2,10 @@ package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
+import net.corda.core.identity.Party
import net.corda.core.identity.excludeHostNode
import net.corda.core.identity.groupAbstractPartyByWellKnownParty
+import net.corda.core.node.StatesToRecord
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.finance.contracts.DealState
@@ -23,7 +25,6 @@ object AutoOfferFlow {
@InitiatingFlow
@StartableByRPC
class Requester(val dealToBeOffered: DealState) : FlowLogic