diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 198a957a38..4471dd86d8 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -10,6 +10,8 @@
+
+
diff --git a/build.gradle b/build.gradle
index 2408dffeb7..2da8daa2e8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
// Our version: bump this on release.
- ext.corda_release_version = "0.16-SNAPSHOT"
+ ext.corda_release_version = "3.0-ENT-RC01"
// Increment this on any release that changes public APIs anywhere in the Corda platform
ext.corda_platform_version = constants.getProperty("platformVersion")
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
@@ -20,7 +20,21 @@ buildscript {
ext.capsule_version = '1.0.1'
ext.asm_version = '0.5.3'
- ext.artemis_version = '2.4.0'
+
+ /*
+ * TODO Upgrade to version 2.4 for large message streaming support
+ *
+ * Due to a memory leak in the connection handling code in Artemis, we are
+ * temporarily downgrading to version 2.2.0.
+ *
+ * The memory leak essentially triggers an out-of-memory exception within
+ * less than 10 seconds and can take down a node if a non-TLS connection is
+ * attempted against the P2P port.
+ *
+ * The issue has been reported to upstream:
+ * https://issues.apache.org/jira/browse/ARTEMIS-1559
+ */
+ ext.artemis_version = '2.2.0'
ext.jackson_version = '2.9.2'
ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
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 546d2dc088..65800b7a8f 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,6 +12,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.finance.USD
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
+import net.corda.testing.internal.rigorousMock
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -24,7 +25,7 @@ class JacksonSupportTest {
private companion object {
val SEED = BigInteger.valueOf(20170922L)!!
val mapper = JacksonSupport.createNonRpcMapper()
- val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).pubkey
+ val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
}
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 218258c305..7bfc292895 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
@@ -31,8 +31,8 @@ import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.node.services.Permissions.invokeRpc;
import static net.corda.node.services.Permissions.startFlow;
-import static net.corda.testing.TestConstants.getALICE_NAME;
-import static net.corda.testing.TestConstants.getDUMMY_NOTARY_NAME;
+import static net.corda.testing.TestConstants.ALICE_NAME;
+import static net.corda.testing.TestConstants.DUMMY_NOTARY_NAME;
public class CordaRPCJavaClientTest extends NodeBasedTest {
public CordaRPCJavaClientTest() {
@@ -41,7 +41,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@ClassRule
public static IntegrationTestSchemas databaseSchemas = new IntegrationTestSchemas(IntegrationTestKt.toDatabaseSchemaName(getALICE_NAME()),
- IntegrationTestKt.toDatabaseSchemaName(getDUMMY_NOTARY_NAME()));
+ IntegrationTestKt.toDatabaseSchemaName(DUMMY_NOTARY_NAME));
private List perms = Arrays.asList(
startFlow(CashPaymentFlow.class),
@@ -65,7 +65,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@Before
public void setUp() throws Exception {
super.setUp();
- node = startNode(getALICE_NAME(), 1, singletonList(rpcUser));
+ node = startNode(ALICE_NAME, 1, singletonList(rpcUser));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
}
diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/Measure.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/Measure.kt
similarity index 98%
rename from testing/test-utils/src/main/kotlin/net/corda/testing/Measure.kt
rename to client/rpc/src/test/kotlin/net/corda/client/rpc/Measure.kt
index a44c28ade2..1e288f2741 100644
--- a/testing/test-utils/src/main/kotlin/net/corda/testing/Measure.kt
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/Measure.kt
@@ -1,4 +1,4 @@
-package net.corda.testing
+package net.corda.client.rpc
import net.corda.core.internal.uncheckedCast
import kotlin.reflect.KCallable
diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt
index 22c160a1d0..5ea77a66fd 100644
--- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt
@@ -9,9 +9,9 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis
import net.corda.node.services.messaging.RPCServerConfiguration
+import net.corda.testing.internal.testThreadFactory
import net.corda.testing.node.internal.RPCDriverDSL
import net.corda.testing.node.internal.rpcDriver
-import net.corda.testing.internal.testThreadFactory
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet
import org.junit.After
import org.junit.Test
diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt
index 85d3351ade..b945487cb9 100644
--- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt
@@ -12,7 +12,6 @@ import net.corda.testing.node.internal.performance.startPublishingFixedRateInjec
import net.corda.testing.node.internal.performance.startReporter
import net.corda.testing.node.internal.performance.startTightLoopInjector
import net.corda.testing.node.internal.rpcDriver
-import net.corda.testing.measure
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt
index 9d57aa401e..fae5fbc6d5 100644
--- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt
+++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt
@@ -1,6 +1,9 @@
package net.corda.client.rpc
+import net.corda.core.flows.FlowLogic
+import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
+import net.corda.node.services.Permissions
import net.corda.node.services.messaging.rpcContext
import net.corda.nodeapi.internal.config.User
import net.corda.testing.node.internal.RPCDriverDSL
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 bc5ad98a35..2c94e44717 100644
--- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt
+++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt
@@ -15,7 +15,7 @@ class SwapIdentitiesFlowTests {
@Before
fun setup() {
// We run this in parallel threads to help catch any race conditions that may exist.
- mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true)
+ mockNet = MockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
}
@Test
diff --git a/constants.properties b/constants.properties
index 0434adb857..935fb23f75 100644
--- a/constants.properties
+++ b/constants.properties
@@ -1,4 +1,4 @@
-gradlePluginsVersion=3.0.1
+gradlePluginsVersion=3.0.2
kotlinVersion=1.1.60
platformVersion=1
guavaVersion=21.0
diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
index ccae80d47e..cd153a18c0 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -63,7 +63,7 @@ import javax.crypto.spec.SecretKeySpec
*/
object Crypto {
/**
- * RSA_SHA256 signature scheme using SHA256 as hash algorithm.
+ * RSA signature scheme using SHA256 for message hashing.
* Note: Recommended key size >= 3072 bits.
*/
@JvmField
@@ -80,7 +80,7 @@ object Crypto {
"RSA_SHA256 signature scheme using SHA256 as hash algorithm."
)
- /** ECDSA signature scheme using the secp256k1 Koblitz curve. */
+ /** ECDSA signature scheme using the secp256k1 Koblitz curve and SHA256 for message hashing. */
@JvmField
val ECDSA_SECP256K1_SHA256 = SignatureScheme(
2,
@@ -95,7 +95,7 @@ object Crypto {
"ECDSA signature scheme using the secp256k1 Koblitz curve."
)
- /** ECDSA signature scheme using the secp256r1 (NIST P-256) curve. */
+ /** ECDSA signature scheme using the secp256r1 (NIST P-256) curve and SHA256 for message hashing. */
@JvmField
val ECDSA_SECP256R1_SHA256 = SignatureScheme(
3,
@@ -110,7 +110,7 @@ object Crypto {
"ECDSA signature scheme using the secp256r1 (NIST P-256) curve."
)
- /** EdDSA signature scheme using the ed255519 twisted Edwards curve. */
+ /** EdDSA signature scheme using the ed25519 twisted Edwards curve and SHA512 for message hashing. */
@JvmField
val EDDSA_ED25519_SHA512 = SignatureScheme(
4,
@@ -127,13 +127,15 @@ object Crypto {
"EdDSA signature scheme using the ed25519 twisted Edwards curve."
)
- /**
- * SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers
- * at the cost of larger key sizes and loss of compatibility.
- */
+ /** DLSequence (ASN1Sequence) for SHA512 truncated to 256 bits, used in SPHINCS-256 signature scheme. */
@JvmField
val SHA512_256 = DLSequence(arrayOf(NISTObjectIdentifiers.id_sha512_256))
+ /**
+ * SPHINCS-256 hash-based signature scheme using SHA512 for message hashing. It provides 128bit security against
+ * post-quantum attackers at the cost of larger key nd signature sizes and loss of compatibility.
+ */
+ // TODO: change val name to SPHINCS256_SHA512. This will break backwards compatibility.
@JvmField
val SPHINCS256_SHA256 = SignatureScheme(
5,
@@ -149,7 +151,8 @@ object Crypto {
"at the cost of larger key sizes and loss of compatibility."
)
- /** Corda composite key type. */
+ /** Corda [CompositeKey] signature type. */
+ // TODO: change the val name to a more descriptive one as it's now confusing and looks like a Key type.
@JvmField
val COMPOSITE_KEY = SignatureScheme(
6,
diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
index 0db44ba841..7bd11ec661 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt
@@ -22,7 +22,7 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
* @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect).
*/
@Throws(InvalidKeyException::class, SignatureException::class)
- fun verify(content: ByteArray) = by.verify(content, this)
+ fun verify(content: ByteArray): Boolean = by.verify(content, this)
/**
* Utility to simplify the act of verifying a signature.
@@ -32,7 +32,7 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
* @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect).
*/
@Throws(InvalidKeyException::class, SignatureException::class)
- fun verify(content: OpaqueBytes) = by.verify(content.bytes, this)
+ fun verify(content: OpaqueBytes): Boolean = by.verify(content.bytes, this)
/**
* Utility to simplify the act of verifying a signature. In comparison to [verify] doesn't throw an
@@ -45,7 +45,8 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
* @return whether the signature is correct for this key.
*/
@Throws(InvalidKeyException::class, SignatureException::class)
- fun isValid(content: ByteArray) = by.isValid(content, this)
- fun withoutKey() : DigitalSignature = DigitalSignature(this.bytes)
+ fun isValid(content: ByteArray): Boolean = by.isValid(content, this)
+
+ fun withoutKey(): DigitalSignature = DigitalSignature(this.bytes)
}
}
diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt
index c3e394b6b5..0b6a3ddeb7 100644
--- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt
+++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt
@@ -2,6 +2,7 @@ package net.corda.core.identity
import com.google.common.collect.ImmutableSet
import net.corda.core.internal.LegalNameValidator
+import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.x500Name
import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.asn1.ASN1Encodable
@@ -78,8 +79,9 @@ data class CordaX500Name(val commonName: String?,
const val MAX_LENGTH_ORGANISATION_UNIT = 64
const val MAX_LENGTH_COMMON_NAME = 64
private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU)
- private val countryCodes: Set = ImmutableSet.copyOf(Locale.getISOCountries())
-
+ @VisibleForTesting
+ val unspecifiedCountry = "ZZ"
+ private val countryCodes: Set = ImmutableSet.copyOf(Locale.getISOCountries() + unspecifiedCountry)
@JvmStatic
fun build(principal: X500Principal): CordaX500Name {
val x500Name = X500Name.getInstance(principal.encoded)
diff --git a/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt b/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt
index 725e75da0f..973de9e942 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt
@@ -12,4 +12,5 @@ import net.corda.core.serialization.CordaSerializable
*/
@CordaSerializable
class MissingContractAttachments(val states: List>)
- : FlowException("Cannot find contract attachments for ${states.map { it.contract }.distinct()}")
\ No newline at end of file
+ : FlowException("Cannot find contract attachments for ${states.map { it.contract }.distinct()}. " +
+ "See https://docs.corda.net/api-contract-constraints.html#debugging")
\ No newline at end of file
diff --git a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java
index 35be3604ae..2661d2b118 100644
--- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java
+++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java
@@ -13,21 +13,22 @@ import org.junit.Test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import static java.util.Collections.emptyList;
import static net.corda.testing.CoreTestUtils.singleIdentity;
import static net.corda.testing.node.NodeTestUtils.startFlow;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.Assert.fail;
public class FlowsInJavaTest {
- private final MockNetwork mockNet = new MockNetwork();
+ private final MockNetwork mockNet = new MockNetwork(emptyList());
private StartedNode aliceNode;
private StartedNode bobNode;
private Party bob;
@Before
public void setUp() throws Exception {
- aliceNode = mockNet.createPartyNode(TestConstants.getALICE_NAME());
- bobNode = mockNet.createPartyNode(TestConstants.getBOB_NAME());
+ aliceNode = mockNet.createPartyNode(TestConstants.ALICE_NAME);
+ bobNode = mockNet.createPartyNode(TestConstants.BOB_NAME);
bob = singleIdentity(bobNode.getInfo());
}
diff --git a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt
index 830398964c..78f1586d8a 100644
--- a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt
+++ b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt
@@ -3,7 +3,7 @@ package net.corda.core.concurrent
import com.nhaarman.mockito_kotlin.*
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.utilities.getOrThrow
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import org.slf4j.Logger
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 460fda3328..f7f1abf245 100644
--- a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt
+++ b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt
@@ -9,6 +9,7 @@ import net.corda.core.node.ServicesForResolution
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2
+import net.corda.testing.internal.rigorousMock
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
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 12ea44c72b..f20a4b20c9 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
@@ -9,7 +9,7 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String
import net.corda.nodeapi.internal.crypto.*
-import net.corda.testing.kryoSpecific
+import net.corda.testing.internal.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import org.junit.Rule
import org.junit.Test
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 3968d8cae3..18ecbcfd24 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt
@@ -14,6 +14,10 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
+import net.corda.testing.dsl.LedgerDSL
+import net.corda.testing.dsl.TestLedgerDSLInterpreter
+import net.corda.testing.dsl.TestTransactionDSLInterpreter
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import org.junit.Before
@@ -31,9 +35,9 @@ class PartialMerkleTreeTest {
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val MEGA_CORP get() = megaCorp.party
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
- val MINI_CORP_PUBKEY get() = miniCorp.pubkey
+ val MINI_CORP_PUBKEY get() = miniCorp.publicKey
}
@Rule
@@ -51,7 +55,7 @@ class PartialMerkleTreeTest {
hashed = nodes.map { it.serialize().sha256() }
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
merkleTree = MerkleTree.getMerkleTree(hashed)
- testLedger = MockServices(rigorousMock().also {
+ testLedger = MockServices(emptyList(), rigorousMock().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
unverifiedTransaction {
diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt
index c11589b8e9..527fd33a49 100644
--- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt
+++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt
@@ -29,7 +29,7 @@ class AttachmentTests {
@Before
fun setUp() {
- mockNet = MockNetwork()
+ mockNet = MockNetwork(emptyList())
}
@After
diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt
index 944e7eaca5..6da0ce7f18 100644
--- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt
+++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt
@@ -14,6 +14,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockServices
import net.corda.testing.node.startFlow
diff --git a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt
index d90bf3e2e6..237474c902 100644
--- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt
+++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt
@@ -13,7 +13,7 @@ import org.junit.After
import org.junit.Test
class ReceiveMultipleFlowTests {
- private val mockNet = MockNetwork()
+ private val mockNet = MockNetwork(emptyList())
private val nodes = (0..2).map { mockNet.createPartyNode() }
@After
fun stopNodes() {
diff --git a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt
index 0b52c89491..d04fae86bb 100644
--- a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt
+++ b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt
@@ -4,7 +4,7 @@ import com.nhaarman.mockito_kotlin.*
import net.corda.core.concurrent.CordaFuture
import net.corda.core.internal.join
import net.corda.core.utilities.getOrThrow
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions
import org.junit.Test
import org.slf4j.Logger
diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt
index 8d9303db22..940d959860 100644
--- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt
+++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt
@@ -70,7 +70,7 @@ class AttachmentSerializationTest {
@Before
fun setUp() {
- mockNet = MockNetwork()
+ mockNet = MockNetwork(emptyList())
server = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
client = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
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 ca795cf4f0..8f70d9610d 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.core.transactions.TransactionBuilder
import net.corda.core.utilities.seconds
import net.corda.finance.POUNDS
import net.corda.testing.*
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.junit.Before
import org.junit.Rule
@@ -25,9 +26,9 @@ class TransactionSerializationTests {
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
val DUMMY_NOTARY get() = dummyNotary.party
- val DUMMY_NOTARY_KEY get() = dummyNotary.key
+ val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
val MEGA_CORP get() = megaCorp.party
- val MEGA_CORP_KEY get() = megaCorp.key
+ val MEGA_CORP_KEY get() = megaCorp.keyPair
}
@Rule
@@ -106,7 +107,7 @@ class TransactionSerializationTests {
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
val ptx2 = notaryServices.signInitialTransaction(tx2)
- val dummyServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
+ val dummyServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
val stx2 = dummyServices.addSignature(ptx2)
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()
@@ -124,8 +125,7 @@ class TransactionSerializationTests {
@Test
fun storeAndLoadWhenSigning() {
val ptx = megaCorpServices.signInitialTransaction(tx)
- ptx.verifySignaturesExcept(notaryServices.key.public)
-
+ ptx.verifySignaturesExcept(DUMMY_NOTARY_KEY.public)
val stored = ptx.serialize()
val loaded = stored.deserialize()
diff --git a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt
index b343a5ed2a..fe7e977962 100644
--- a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt
+++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt
@@ -10,6 +10,7 @@ import net.corda.core.identity.Party
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.junit.Before
import org.junit.Rule
@@ -28,7 +29,7 @@ class LedgerTransactionQueryTests {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val keyPair = generateKeyPair()
- private val services = MockServices(rigorousMock().also {
+ private val services = MockServices(emptyList(), rigorousMock().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, CordaX500Name("MegaCorp", "London", "GB"), keyPair)
private val identity: Party = services.myInfo.singleIdentity()
diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt
index 1f487f6808..3e923c5a7d 100644
--- a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt
+++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt
@@ -12,6 +12,7 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import org.junit.Rule
@@ -27,7 +28,7 @@ class TransactionEncumbranceTests {
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
val MEGA_CORP get() = megaCorp.party
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
}
@Rule
@@ -61,7 +62,7 @@ class TransactionEncumbranceTests {
}
}
- private val ledgerServices = MockServices(rigorousMock().also {
+ private val ledgerServices = MockServices(emptyList(), rigorousMock().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name)
diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt
index 8280d688e6..8191dce1ae 100644
--- a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt
+++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt
@@ -6,6 +6,7 @@ import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.Party
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
+import net.corda.testing.internal.rigorousMock
import org.junit.Rule
import org.junit.Test
import java.math.BigInteger
@@ -23,7 +24,7 @@ class TransactionTests {
val BOB = TestIdentity(BOB_NAME, 80).party
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val DUMMY_NOTARY get() = dummyNotary.party
- val DUMMY_NOTARY_KEY get() = dummyNotary.key
+ val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
}
@Rule
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 8d7cb3e12f..0a26639ae4 100644
--- a/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt
+++ b/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt
@@ -7,9 +7,10 @@ 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.withTestSerialization
+import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Suite
@@ -36,6 +37,10 @@ class NonEmptySetTest {
}
class General {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
+
@Test
fun `copyOf - empty source`() {
assertThatThrownBy { NonEmptySet.copyOf(HashSet()) }.isInstanceOf(IllegalArgumentException::class.java)
@@ -48,11 +53,9 @@ class NonEmptySetTest {
@Test
fun `serialize deserialize`() {
- withTestSerialization {
- val original = NonEmptySet.of(-17, 22, 17)
- val copy = original.serialize().deserialize()
- assertThat(copy).isEqualTo(original).isNotSameAs(original)
- }
+ val original = NonEmptySet.of(-17, 22, 17)
+ val copy = original.serialize().deserialize()
+ assertThat(copy).isEqualTo(original).isNotSameAs(original)
}
}
diff --git a/docs/source/api-contract-constraints.rst b/docs/source/api-contract-constraints.rst
index 6f7fad2ff4..4a200e1756 100644
--- a/docs/source/api-contract-constraints.rst
+++ b/docs/source/api-contract-constraints.rst
@@ -1,15 +1,18 @@
API: Contract Constraints
=========================
-A basic understanding of contract key concepts, which can be found :doc:`here `,
-is required reading for this page.
+.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
+.. contents::
+
+Contract constraints
+--------------------
Transaction states specify a constraint over the contract that will be used to verify it. For a transaction to be
valid, the ``verify`` function associated with each state must run successfully. However, for this to be secure, it is
not sufficient to specify the ``verify`` function by name as there may exist multiple different implementations with
the same method signature and enclosing class. Contract constraints solve this problem by allowing a contract developer
to constrain which ``verify`` functions out of the universe of implementations can be used (i.e. the universe is
-everything that matches the signature and contract constraints restricts this universe to a subset).
+everything that matches the signature and contract constraints restrict this universe to a subset).
A typical constraint is the hash of the CorDapp JAR that contains the contract and states but will in future releases
include constraints that require specific signers of the JAR, or both the signer and the hash. Constraints can be
@@ -60,10 +63,10 @@ that they were loaded from. This makes it possible to find the attachment for an
automatic resolution of attachments is done by the ``TransactionBuilder`` and how, when verifying the constraints and
contracts, attachments are associated with their respective contracts.
-Implementations
----------------
+Implementations of AttachmentConstraint
+---------------------------------------
-There are three implementations of ``AttachmentConstraints`` with more planned in the future.
+There are three implementations of ``AttachmentConstraint`` with more planned in the future.
``AlwaysAcceptAttachmentConstraint``: Any attachment (except a missing one) will satisfy this constraint.
@@ -137,3 +140,18 @@ Full Nodes
**********
When testing against full nodes simply place your CorDapp into the cordapps directory of the node.
+
+Debugging
+---------
+If an attachment constraint cannot be resolved, a ``MissingContractAttachments`` exception is thrown. There are two
+common sources of ``MissingContractAttachments`` exceptions:
+
+Not setting CorDapp packages in tests
+*************************************
+You are running a test and have not specified the CorDapp packages to scan. See the instructions above.
+
+Wrong fully-qualified contract name
+***********************************
+You are specifying the fully-qualified name of the contract incorrectly. For example, you've defined ``MyContract`` in
+the package ``com.mycompany.myapp.contracts``, but the fully-qualified contract name you pass to the
+``TransactionBuilder`` is ``com.mycompany.myapp.MyContract`` (instead of ``com.mycompany.myapp.contracts.MyContract``).
\ No newline at end of file
diff --git a/docs/source/api-vault-query.rst b/docs/source/api-vault-query.rst
index b0f295268e..1adb4631b9 100644
--- a/docs/source/api-vault-query.rst
+++ b/docs/source/api-vault-query.rst
@@ -55,8 +55,8 @@ Helper methods are also provided with default values for arguments:
The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of
filter criteria:
-- Use ``queryBy`` to obtain a only current snapshot of data (for a given ``QueryCriteria``)
-- Use ``trackBy`` to obtain a both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
+- Use ``queryBy`` to obtain a current snapshot of data (for a given ``QueryCriteria``)
+- Use ``trackBy`` to obtain both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
.. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL)
diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst
index a1373f9849..e71d285e93 100644
--- a/docs/source/building-a-cordapp-index.rst
+++ b/docs/source/building-a-cordapp-index.rst
@@ -6,6 +6,7 @@ CorDapps
cordapp-overview
writing-a-cordapp
+ upgrade-notes
cordapp-build-systems
building-against-master
corda-api
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
index a5d6753bdf..1bdeb80ede 100644
--- a/docs/source/changelog.rst
+++ b/docs/source/changelog.rst
@@ -6,6 +6,50 @@ from the previous milestone release.
UNRELEASED
----------
+
+* The network map service concept has been re-designed. More information can be found in :doc:`network-map`.
+
+ * The previous design was never intended to be final but was rather a quick implementation in the earliest days of the
+ Corda project to unblock higher priority items. It suffers from numerous disadvantages including lack of scalability,
+ as one node is expected to hold open and manage connections to every node on the network; not reliable; hard to defend
+ against DoS attacks; etc.
+
+ * There is no longer a special network map node for distributing the network map to the other nodes. Instead the network
+ map is now a collection of signed ``NodeInfo`` files distributed via HTTP.
+
+ * The ``certificateSigningService`` config has been replaced by ``compatibilityZoneURL`` which is the base URL for the
+ doorman registration and for downloading the network map. There is also an end-point for the node to publish its node-info
+ object, which the node does each time it changes. ``networkMapService`` config has been removed.
+
+ * To support local and test deployments, the node polls the ``additional-node-infos`` directory for these signed ``NodeInfo``
+ objects which are stored in its local cache. On startup the node generates its own signed file with the filename format
+ "nodeInfo-*". This can be copied to every node's ``additional-node-infos`` directory that is part of the network.
+
+ * Cordform (which is the ``deployNodes`` gradle task) does this copying automatically for the demos. The ``NetworkMap``
+ parameter is no longer needed.
+
+ * For test deployments we've introduced a bootstrapping tool (see :doc:`setting-up-a-corda-network`).
+
+ * ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` configs have been
+ removed. The configuration of notaries has been simplified into a single ``notary`` config object. See
+ :doc:`corda-configuration-file` for more details.
+
+ * Introducing the concept of network parameters which are a set of constants which all nodes on a network must agree on
+ to correctly interop.
+
+ * The set of valid notaries has been moved to the network parameters. Notaries are no longer identified by the CN in
+ their X500 name.
+
+ * Single node notaries no longer have a second separate notary identity. Their main identity *is* their notary identity.
+ Use ``NetworkMapCache.notaryIdentities`` to get the list of available notaries.
+
+ * The common name in the node's X500 legal name is no longer reserved and can be used as part of the node's name.
+
+ * Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
+ was needed to allow changes to the schema.
+
+* Support for external user credentials data source and password encryption [CORDA-827].
+
* Integrate database migration tool: http://www.liquibase.org/ :
* The migration files are split per ``MappedSchemas``. (added new property: migrationResource used to point to the resource file containing the db changes corresponding to the JPA entities)
* config flag ``database.initialiseSchema`` was renamed to: ``database.runMigration`` (if true then the migration is run during startup just before hibernate is initialised.)
@@ -45,24 +89,12 @@ UNRELEASED
deploys nodes for development and testing, the latter turns a project into a cordapp project that generates JARs in
the standard CorDapp format.
-* ``Cordform`` and node identity generation:
- * Removed the parameter ``NetworkMap`` from Cordform. Now at the end of the deployment the following happens:
- 1. Each node is started and its signed serialized NodeInfo is written to disk in the node base directory.
- 2. Every serialized ``NodeInfo`` above is copied in every other node "additional-node-info" folder under the NodeInfo folder.
-
-* Nodes read and poll the filesystem for serialized ``NodeInfo`` in the ``additional-node-info`` directory.
-
* ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup.
* Enums now respect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't
either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is
thrown.
-* ``extraAdvertisedServiceIds`` config has been removed as part of the previous work to retire the concept of advertised
- services. The remaining use of this config was for notaries, the configuring of which has been cleaned up and simplified.
- ``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
to be appended to node.conf.
@@ -79,8 +111,8 @@ UNRELEASED
allows services to start flows marked with the ``StartableByService`` annotation. For backwards compatability
service classes with only ``ServiceHub`` constructors will still work.
-* ``TimeWindow`` now has a ``length`` property that returns the length of the time-window, or ``null`` if the
- time-window is open-ended.
+* ``TimeWindow`` now has a ``length`` property that returns the length of the time-window as a ``java.time.Duration`` object,
+ or ``null`` if the time-window isn't closed.
* A new ``SIGNERS_GROUP`` with ordinal 6 has been added to ``ComponentGroupEnum`` that corresponds to the ``Command``
signers.
@@ -96,18 +128,16 @@ UNRELEASED
* 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`.
-* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This is
- needed to allow new ``node_info_hash`` column to be added for the network map redesign work.
-
* Added an overload of ``TransactionWithSignatures.verifySignaturesExcept`` which takes in a collection of ``PublicKey``s.
-* Replaced node configuration parameter ``certificateSigningService`` with ``compatibilityZoneURL``, which is Corda
- compatibility zone network management service's address.
+* ``DriverDSLExposedInterface`` has been renamed to ``DriverDSL`` and the ``waitForAllNodesToFinish()`` method has instead
+ become a parameter on driver creation.
-* ``waitForAllNodesToFinish()`` method in ``DriverDSLExposedInterface`` has instead become a parameter on driver creation.
+* Values for the ``database.transactionIsolationLevel`` config now follow the ``java.sql.Connection`` int constants but
+ without the "TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc.
-* ``database.transactionIsolationLevel`` values now follow the ``java.sql.Connection`` int constants but without the
- "TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc.
+* Peer-to-peer communications is now via AMQP 1.0 as default.
+ Although the legacy Artemis CORE bridging can still be used by setting the ``useAMQPBridges`` configuration property to false.
* Enterprise Corda only: Compatibility with SQL Server 2017 and SQL Azure databases.
diff --git a/docs/source/clientrpc.rst b/docs/source/clientrpc.rst
index 14d29ba9b9..ae30828149 100644
--- a/docs/source/clientrpc.rst
+++ b/docs/source/clientrpc.rst
@@ -26,7 +26,7 @@ permissions that RPC can use for fine-grain access control.
These users are added to the node's ``node.conf`` file.
-The syntax for adding an RPC user is:
+The simplest way of adding an RPC user is to include it in the ``rpcUsers`` list:
.. container:: codeset
@@ -59,9 +59,6 @@ Users need permissions to invoke any RPC call. By default, nothing is allowed. T
...
]
-.. note:: Currently, the node's web server has super-user access, meaning that it can run any RPC operation without
- logging in. This will be changed in a future release.
-
Permissions Syntax
^^^^^^^^^^^^^^^^^^
@@ -71,6 +68,134 @@ Fine grained permissions allow a user to invoke a specific RPC operation, or to
- to invoke a RPC operation: ``InvokeRpc.`` e.g., ``InvokeRpc.nodeInfo``.
.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows.
+RPC security management
+-----------------------
+
+Setting ``rpcUsers`` provides a simple way of granting RPC permissions to a fixed set of users, but has some
+obvious shortcomings. To support use cases aiming for higher security and flexibility, Corda offers additional security
+features such as:
+
+ * Fetching users credentials and permissions from an external data source (e.g.: a remote RDBMS), with optional in-memory
+ caching. In particular, this allows credentials and permissions to be updated externally without requiring nodes to be
+ restarted.
+ * Password stored in hash-encrypted form. This is regarded as must-have when security is a concern. Corda currently supports
+ a flexible password hash format conforming to the Modular Crypt Format provided by the `Apache Shiro framework `_
+
+These features are controlled by a set of options nested in the ``security`` field of ``node.conf``.
+The following example shows how to configure retrieval of users credentials and permissions from a remote database with
+passwords in hash-encrypted format and enable in-memory caching of users data:
+
+.. container:: codeset
+
+ .. sourcecode:: groovy
+
+ security = {
+ authService = {
+ dataSource = {
+ type = "DB",
+ passwordEncryption = "SHIRO_1_CRYPT",
+ connection = {
+ jdbcUrl = ""
+ username = ""
+ password = ""
+ driverClassName = ""
+ }
+ }
+ options = {
+ cache = {
+ expireAfterSecs = 120
+ maxEntries = 10000
+ }
+ }
+ }
+ }
+
+It is also possible to have a static list of users embedded in the ``security`` structure by specifying a ``dataSource``
+of ``INMEMORY`` type:
+
+.. container:: codeset
+
+ .. sourcecode:: groovy
+
+ security = {
+ authService = {
+ dataSource = {
+ type = "INMEMORY",
+ users = [
+ {
+ username = "",
+ password = "",
+ permissions = ["", "", ...]
+ },
+ ...
+ ]
+ }
+ }
+ }
+
+.. warning:: A valid configuration cannot specify both the ``rpcUsers`` and ``security`` fields. Doing so will trigger
+ an exception at node startup.
+
+Authentication/authorisation data
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``dataSource`` structure defines the data provider supplying credentials and permissions for users. There exist two
+supported types of such data source, identified by the ``dataSource.type`` field:
+
+ :INMEMORY: A static list of user credentials and permissions specified by the ``users`` field.
+
+ :DB: An external RDBMS accessed via the JDBC connection described by ``connection``. Note that, unlike the ``INMEMORY``
+ case, in a user database permissions are assigned to *roles* rather than individual users. The current implementation
+ expects the database to store data according to the following schema:
+
+ - Table ``users`` containing columns ``username`` and ``password``. The ``username`` column *must have unique values*.
+ - Table ``user_roles`` containing columns ``username`` and ``role_name`` associating a user to a set of *roles*.
+ - Table ``roles_permissions`` containing columns ``role_name`` and ``permission`` associating a role to a set of
+ permission strings.
+
+ .. note:: There is no prescription on the SQL type of each column (although our tests were conducted on ``username`` and
+ ``role_name`` declared of SQL type ``VARCHAR`` and ``password`` of ``TEXT`` type). It is also possible to have extra columns
+ in each table alongside the expected ones.
+
+Password encryption
+^^^^^^^^^^^^^^^^^^^
+
+Storing passwords in plain text is discouraged in applications where security is critical. Passwords are assumed
+to be in plain format by default, unless a different format is specified by the ``passwordEncryption`` field, like:
+
+.. container:: codeset
+
+ .. sourcecode:: groovy
+
+ passwordEncryption = SHIRO_1_CRYPT
+
+``SHIRO_1_CRYPT`` identifies the `Apache Shiro fully reversible
+Modular Crypt Format `_,
+it is currently the only non-plain password hash-encryption format supported. Hash-encrypted passwords in this
+format can be produced by using the `Apache Shiro Hasher command line tool `_.
+
+Caching user accounts data
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A cache layer on top of the external data source of users credentials and permissions can significantly improve
+performances in some cases, with the disadvantage of causing a (controllable) delay in picking up updates to the underlying data.
+Caching is disabled by default, it can be enabled by defining the ``options.cache`` field in ``security.authService``,
+for example:
+
+.. container:: codeset
+
+ .. sourcecode:: groovy
+
+ options = {
+ cache = {
+ expireAfterSecs = 120
+ maxEntries = 10000
+ }
+ }
+
+This will enable a non-persistent cache contained in the node's memory with maximum number of entries set to ``maxEntries``
+where entries are expired and refreshed after ``expireAfterSecs`` seconds.
+
Observables
-----------
The RPC system handles observables in a special way. When a method returns an observable, whether directly or
diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst
index 7ac54bbe95..71e292e1d6 100644
--- a/docs/source/corda-configuration-file.rst
+++ b/docs/source/corda-configuration-file.rst
@@ -101,6 +101,9 @@ path to the node's base directory.
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
+:security: Contains various nested fields controlling user authentication/authorization, in particular for RPC accesses. See
+ :doc:`clientrpc` for details.
+
:webAddress: The host and port on which the webserver will listen if it is started. This is not used by the node itself.
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
@@ -191,4 +194,7 @@ path to the node's base directory.
:sshPort: Port to be used for SSH connection, default ``22``.
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
- Default Jolokia access url is http://127.0.0.1:7005/jolokia/
\ No newline at end of file
+ Default Jolokia access url is http://127.0.0.1:7005/jolokia/
+
+:useAMQPBridges: Optionally can be set to ``false`` to use Artemis CORE Bridges for peer-to-peer communications.
+ Otherwise, defaults to ``true`` and the AMQP 1.0 protocol will be used for message transfer between nodes.
\ No newline at end of file
diff --git a/docs/source/corda-nodes-index.rst b/docs/source/corda-nodes-index.rst
index 061902b1aa..c1dfa0b508 100644
--- a/docs/source/corda-nodes-index.rst
+++ b/docs/source/corda-nodes-index.rst
@@ -10,7 +10,6 @@ Corda nodes
corda-configuration-file
clientrpc
shell
- node-auth-config
node-database
node-administration
out-of-process-verification
\ No newline at end of file
diff --git a/docs/source/example-code/build.gradle b/docs/source/example-code/build.gradle
index db3ad5da93..50464033a5 100644
--- a/docs/source/example-code/build.gradle
+++ b/docs/source/example-code/build.gradle
@@ -36,6 +36,7 @@ dependencies {
compile project(':core')
compile project(':client:jfx')
compile project(':node-driver')
+ compile project(':webserver')
testCompile project(':verifier')
testCompile project(':test-utils')
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/TemplateContract.java b/docs/source/example-code/src/main/java/com/template/TemplateContract.java
similarity index 93%
rename from docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/TemplateContract.java
rename to docs/source/example-code/src/main/java/com/template/TemplateContract.java
index a46712bb15..5c4278a408 100644
--- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/TemplateContract.java
+++ b/docs/source/example-code/src/main/java/com/template/TemplateContract.java
@@ -1,4 +1,4 @@
-package net.corda.docs.java.tutorial.helloworld;
+package com.template;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.Contract;
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java
index cc0b20faed..fd35ab75ac 100644
--- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java
+++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java
@@ -1,17 +1,21 @@
package net.corda.docs.java.tutorial.helloworld;
-// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable;
+import com.template.TemplateContract;
+import net.corda.core.flows.*;
+
+// DOCSTART 01
+// Add these imports:
import net.corda.core.contracts.Command;
import net.corda.core.contracts.CommandData;
-import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
-import static net.corda.docs.java.tutorial.helloworld.TemplateContract.TEMPLATE_CONTRACT_ID;
+import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID;
+// Replace TemplateFlow's definition with:
@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic {
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUState.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUState.java
index 977457fd29..f724508648 100644
--- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUState.java
+++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUState.java
@@ -1,13 +1,15 @@
package net.corda.docs.java.tutorial.helloworld;
-// DOCSTART 01
-import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
-import net.corda.core.identity.Party;
-
import java.util.List;
+// DOCSTART 01
+// Add these imports:
+import com.google.common.collect.ImmutableList;
+import net.corda.core.identity.Party;
+
+// Replace TemplateState's definition with:
public class IOUState implements ContractState {
private final int value;
private final Party lender;
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
index 6c83cabfc6..0339b73754 100644
--- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
+++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
@@ -3,48 +3,33 @@ package net.corda.docs.java.tutorial.testdsl;
import kotlin.Unit;
import net.corda.core.contracts.PartyAndReference;
import net.corda.core.identity.CordaX500Name;
-import net.corda.core.identity.Party;
import net.corda.finance.contracts.ICommercialPaperState;
import net.corda.finance.contracts.JavaCommercialPaper;
import net.corda.finance.contracts.asset.Cash;
-import net.corda.node.services.api.IdentityServiceInternal;
-import net.corda.testing.SerializationEnvironmentRule;
import net.corda.testing.node.MockServices;
import net.corda.testing.TestIdentity;
-import org.junit.Rule;
import org.junit.Test;
import java.security.PublicKey;
import java.time.temporal.ChronoUnit;
+import static java.util.Collections.emptyList;
import static net.corda.core.crypto.Crypto.generateKeyPair;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.Currencies.issuedBy;
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
+import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
import static net.corda.testing.node.NodeTestUtils.ledger;
import static net.corda.testing.node.NodeTestUtils.transaction;
-import static net.corda.testing.CoreTestUtils.rigorousMock;
import static net.corda.testing.TestConstants.*;
-import static org.mockito.Mockito.doReturn;
public class CommercialPaperTest {
- private static final TestIdentity ALICE = new TestIdentity(getALICE_NAME(), 70L);
+ private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L);
private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic();
- private static final TestIdentity BOB = new TestIdentity(getBOB_NAME(), 80L);
+ private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L);
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
- private static final Party DUMMY_NOTARY = new TestIdentity(getDUMMY_NOTARY_NAME(), 20L).getParty();
- @Rule
- public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
private final byte[] defaultRef = {123};
- private final MockServices ledgerServices;
-
- {
- IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
- doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPubkey());
- doReturn(null).when(identityService).partyFromKey(BIG_CORP_PUBKEY);
- doReturn(null).when(identityService).partyFromKey(ALICE.getPubkey());
- ledgerServices = new MockServices(identityService, MEGA_CORP.getName());
- }
+ private final MockServices ledgerServices = new MockServices(emptyList(), makeTestIdentityService(MEGA_CORP.getIdentity()));
// DOCSTART 1
private ICommercialPaperState getPaper() {
@@ -52,7 +37,7 @@ public class CommercialPaperTest {
MEGA_CORP.ref(defaultRef),
MEGA_CORP.getParty(),
issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)),
- getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
+ TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
);
}
// DOCEND 1
@@ -61,7 +46,7 @@ public class CommercialPaperTest {
@Test
public void simpleCP() {
ICommercialPaperState inState = getPaper();
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.attachments(JCP_PROGRAM_ID);
tx.input(JCP_PROGRAM_ID, inState);
@@ -76,10 +61,10 @@ public class CommercialPaperTest {
@Test
public void simpleCPMove() {
ICommercialPaperState inState = getPaper();
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.verifies();
});
@@ -92,10 +77,10 @@ public class CommercialPaperTest {
@Test
public void simpleCPMoveFails() {
ICommercialPaperState inState = getPaper();
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.failsWith("the state is propagated");
});
@@ -108,10 +93,10 @@ public class CommercialPaperTest {
@Test
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
tx.failsWith("the state is propagated");
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty()));
@@ -125,17 +110,17 @@ public class CommercialPaperTest {
// DOCSTART 6
@Test
public void simpleIssuanceWithTweak() {
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue());
- tw.timeWindow(getTEST_TX_TIME());
+ tw.timeWindow(TEST_TX_TIME);
return tw.failsWith("output states are issued by a command signer");
});
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
- tx.timeWindow(getTEST_TX_TIME());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
+ tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
return Unit.INSTANCE;
@@ -146,16 +131,16 @@ public class CommercialPaperTest {
// DOCSTART 7
@Test
public void simpleIssuanceWithTweakTopLevelTx() {
- transaction(ledgerServices, DUMMY_NOTARY, tx -> {
+ transaction(ledgerServices, tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue());
- tw.timeWindow(getTEST_TX_TIME());
+ tw.timeWindow(TEST_TX_TIME);
return tw.failsWith("output states are issued by a command signer");
});
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
- tx.timeWindow(getTEST_TX_TIME());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
+ tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
}
@@ -165,7 +150,7 @@ public class CommercialPaperTest {
@Test
public void chainCommercialPaper() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
@@ -176,9 +161,9 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
- tx.timeWindow(getTEST_TX_TIME());
+ tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
@@ -188,8 +173,8 @@ public class CommercialPaperTest {
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
- tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
@@ -201,7 +186,7 @@ public class CommercialPaperTest {
@Test
public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
@@ -212,9 +197,9 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
- tx.timeWindow(getTEST_TX_TIME());
+ tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
@@ -224,8 +209,8 @@ public class CommercialPaperTest {
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
- tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
@@ -234,7 +219,7 @@ public class CommercialPaperTest {
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.fails();
@@ -247,7 +232,7 @@ public class CommercialPaperTest {
@Test
public void chainCommercialPaperTweak() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
- ledger(ledgerServices, DUMMY_NOTARY, l -> {
+ ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
@@ -258,9 +243,9 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
- tx.timeWindow(getTEST_TX_TIME());
+ tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
@@ -270,8 +255,8 @@ public class CommercialPaperTest {
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
- tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
@@ -281,7 +266,7 @@ public class CommercialPaperTest {
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
- tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
+ tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
lw.fails();
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java
index b74dfd3ef4..ed0031dc48 100644
--- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java
+++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java
@@ -1,20 +1,25 @@
package net.corda.docs.java.tutorial.twoparty;
-// DOCSTART 01
-import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.CommandData;
-import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.Contract;
-import net.corda.core.identity.Party;
import net.corda.core.transactions.LedgerTransaction;
+// DOCSTART 01
+// Add these imports:
+import com.google.common.collect.ImmutableList;
+import net.corda.core.contracts.CommandWithParties;
+import net.corda.core.identity.Party;
+
import java.security.PublicKey;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
+// Replace TemplateContract's definition with:
public class IOUContract implements Contract {
+ public static final String IOU_CONTRACT_ID = "com.template.IOUContract";
+
// Our Create command.
public static class Create implements CommandData {
}
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java
index 4e0533d323..5ad1a12ce2 100644
--- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java
+++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java
@@ -45,15 +45,14 @@ public class IOUFlow extends FlowLogic {
// We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
+ // DOCSTART 02
// We create a transaction builder.
final TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary);
- // DOCSTART 02
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
- String outputContract = IOUContract.class.getName();
- StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
+ StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.IOU_CONTRACT_ID);
List requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlowResponder.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlowResponder.java
index ac1f312ab6..35b2a3f97b 100644
--- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlowResponder.java
+++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlowResponder.java
@@ -1,16 +1,16 @@
package net.corda.docs.java.tutorial.twoparty;
// DOCSTART 01
+// Add these imports:
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.contracts.ContractState;
import net.corda.core.flows.*;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.utilities.ProgressTracker;
-import net.corda.docs.java.tutorial.helloworld.IOUFlow;
-import net.corda.docs.java.tutorial.helloworld.IOUState;
import static net.corda.core.contracts.ContractsDSL.requireThat;
+// Define IOUFlowResponder:
@InitiatedBy(IOUFlow.class)
public class IOUFlowResponder extends FlowLogic {
private final FlowSession otherPartySession;
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/flow.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/flow.kt
index 24832e76eb..676af37f4c 100644
--- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/flow.kt
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/flow.kt
@@ -1,16 +1,19 @@
package net.corda.docs.tutorial.helloworld
-// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable
-import net.corda.core.contracts.Command
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
+
+// DOCSTART 01
+// Add these imports:
+import net.corda.core.contracts.Command
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
+// Replace TemplateFlow's definition with:
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/state.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/state.kt
index 447265a2ae..19f431bfb1 100644
--- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/state.kt
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/helloworld/state.kt
@@ -1,9 +1,12 @@
package net.corda.docs.tutorial.helloworld
-// DOCSTART 01
import net.corda.core.contracts.ContractState
+
+// DOCSTART 01
+// Add these imports:
import net.corda.core.identity.Party
+// Replace TemplateState's definition with:
class IOUState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt
index 911b7cefde..0147e4e623 100644
--- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt
@@ -67,7 +67,7 @@ class TutorialMockNetwork {
@Before
fun setUp() {
- mockNet = MockNetwork()
+ mockNet = MockNetwork(emptyList())
nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode()
nodeB.registerInitiatedFlow(FlowB::class.java)
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
index 6754a6082c..1fc5f4c832 100644
--- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
@@ -14,6 +14,7 @@ import net.corda.finance.contracts.asset.CASH
import net.corda.finance.contracts.asset.Cash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import net.corda.testing.node.transaction
@@ -28,15 +29,15 @@ class CommercialPaperTest {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val ALICE get() = alice.party
- val ALICE_PUBKEY get() = alice.pubkey
+ val ALICE_PUBKEY get() = alice.publicKey
val MEGA_CORP get() = megaCorp.party
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
- private val ledgerServices = MockServices(rigorousMock().also {
+ private val ledgerServices = MockServices(emptyList(), rigorousMock().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/contract.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/contract.kt
index 96fb3482c1..05fb4af8bd 100644
--- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/contract.kt
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/contract.kt
@@ -1,12 +1,16 @@
package net.corda.docs.tutorial.twoparty
-// DOCSTART 01
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
-import net.corda.core.contracts.requireSingleCommand
-import net.corda.core.contracts.requireThat
import net.corda.core.transactions.LedgerTransaction
+// DOCSTART 01
+// Add these imports:
+import net.corda.core.contracts.*
+
+// Replace IOUContract's contract ID and definition with:
+val IOU_CONTRACT_ID = "com.template.IOUContract"
+
class IOUContract : Contract {
// Our Create command.
class Create : CommandData
@@ -20,7 +24,7 @@ class IOUContract : Contract {
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints.
- val out = tx.outputsOfType().single()
+ val out = tx.outputsOfType().single()
"The IOU's value must be non-negative." using (out.value > 0)
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt
index 0d8ac221ad..27f4705501 100644
--- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt
@@ -6,9 +6,17 @@ import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.*
import net.corda.core.identity.Party
+import net.corda.core.messaging.CordaRPCOps
+import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
-import kotlin.reflect.jvm.jvmName
+import net.corda.webserver.services.WebServerPluginRegistry
+import java.util.function.Function
+import javax.ws.rs.GET
+import javax.ws.rs.Path
+import javax.ws.rs.Produces
+import javax.ws.rs.core.MediaType
+import javax.ws.rs.core.Response
// DOCEND 01
@InitiatingFlow
@@ -25,14 +33,13 @@ class IOUFlow(val iouValue: Int,
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
- // We create a transaction builder
+ // DOCSTART 02
+ // We create a transaction builder.
val txBuilder = TransactionBuilder(notary = notary)
- // DOCSTART 02
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
- val outputContract = IOUContract::class.jvmName
- val outputContractAndState = StateAndContract(outputState, outputContract)
+ val outputContractAndState = StateAndContract(outputState, IOU_CONTRACT_ID)
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder.
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flowResponder.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flowResponder.kt
index b8007cc2ec..f2ff5ecdc9 100644
--- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flowResponder.kt
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flowResponder.kt
@@ -1,16 +1,19 @@
package net.corda.docs.tutorial.twoparty
-// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable
-import net.corda.core.contracts.requireThat
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.SignTransactionFlow
-import net.corda.core.transactions.SignedTransaction
import net.corda.docs.tutorial.helloworld.IOUFlow
import net.corda.docs.tutorial.helloworld.IOUState
+// DOCSTART 01
+// Add these imports:
+import net.corda.core.contracts.requireThat
+import net.corda.core.transactions.SignedTransaction
+
+// Define IOUFlowResponder:
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic() {
@Suspendable
diff --git a/docs/source/hello-world-flow.rst b/docs/source/hello-world-flow.rst
index 9d79832605..817de0545a 100644
--- a/docs/source/hello-world-flow.rst
+++ b/docs/source/hello-world-flow.rst
@@ -6,8 +6,8 @@
Writing the flow
================
-A flow encodes a sequence of steps that a node can run to achieve a specific ledger update. By installing new flows on
-a node, we allow the node to handle new business processes. The flow we define will allow a node to issue an
+A flow encodes a sequence of steps that a node can perform to achieve a specific ledger update. By installing new flows
+on a node, we allow the node to handle new business processes. The flow we define will allow a node to issue an
``IOUState`` onto the ledger.
Flow outline
@@ -40,8 +40,8 @@ FlowLogic
---------
All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``.
-Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete both the existing flows in the
-template, and replace them with the following:
+Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete the two existing flows in the
+template (``Initiator`` and ``Responder``), and replace them with the following:
.. container:: codeset
diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst
index 17d506471d..592cfee44c 100644
--- a/docs/source/hello-world-running.rst
+++ b/docs/source/hello-world-running.rst
@@ -107,9 +107,15 @@ commands.
We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing:
-.. code:: bash
+.. container:: codeset
- start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
+ .. code-block:: java
+
+ start IOUFlow arg0: 99, arg1: "O=PartyB,L=New York,C=US"
+
+ .. code-block:: kotlin
+
+ start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
This single command will cause PartyA and PartyB to automatically agree an IOU. This is one of the great advantages of
the flow framework - it allows you to reduce complex negotiation and update processes into a single function call.
@@ -118,13 +124,7 @@ If the flow worked, it should have recorded a new IOU in the vaults of both Part
We can check the contents of each node's vault by running:
-.. container:: codeset
-
- .. code-block:: java
-
- run vaultQuery contractStateType: com.template.state.IOUState
-
- .. code-block:: kotlin
+.. code-block:: base
run vaultQuery contractStateType: com.template.IOUState
@@ -174,6 +174,11 @@ parts:
* The ``IOUState``, representing IOUs on the ledger
* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
+After completing this tutorial, your CorDapp should look like this:
+
+* Java: https://github.com/corda/corda-tut1-solution-java
+* Kotlin: https://github.com/corda/corda-tut1-solution-kotlin
+
Next steps
----------
There are a number of improvements we could make to this CorDapp:
@@ -183,4 +188,4 @@ There are a number of improvements we could make to this CorDapp:
* We could add an API, to make it easier to interact with the CorDapp
But for now, the biggest priority is to add an ``IOUContract`` imposing constraints on the evolution of each
-``IOUState`` over time. This will be the focus of our next tutorial.
+``IOUState`` over time. This will be the focus of our next tutorial.
\ No newline at end of file
diff --git a/docs/source/hello-world-state.rst b/docs/source/hello-world-state.rst
index 5d9148d333..fb078f0780 100644
--- a/docs/source/hello-world-state.rst
+++ b/docs/source/hello-world-state.rst
@@ -24,7 +24,6 @@ interface is defined as follows:
val participants: List
}
-<<<<<<< HEAD
The first thing you'll probably notice about this interface declaration is that its not written in Java or another
common language. The core Corda platform, including the interface declaration above, is entirely written in Kotlin.
@@ -70,7 +69,7 @@ later is often as simple as adding an additional property to your class definiti
Defining IOUState
-----------------
-Let's get started by opening ``TemplateState.java`` (for Java) or ``App.kt`` (for Kotlin) and updating
+Let's get started by opening ``TemplateState.java`` (for Java) or ``StatesAndContracts.kt`` (for Kotlin) and updating
``TemplateState`` to define an ``IOUState``:
.. container:: codeset
diff --git a/docs/source/hello-world-template.rst b/docs/source/hello-world-template.rst
index 18ad47f515..cf34d8c892 100644
--- a/docs/source/hello-world-template.rst
+++ b/docs/source/hello-world-template.rst
@@ -41,34 +41,33 @@ https://docs.corda.net/tutorial-cordapp.html#opening-the-example-cordapp-in-inte
Template structure
------------------
-The template has a number of files, but we can ignore most of them. To implement our IOU CorDapp in Java, we'll only
-need to modify two files. For Kotlin, we'll simply be modifying the ``App.kt`` file:
+The template has a number of files, but we can ignore most of them. We will only be modifying the following files:
.. container:: codeset
.. code-block:: java
// 1. The state
- src/main/java/com/template/TemplateState.java
+ cordapp-contracts-states/src/main/java/com/template/TemplateState.java
// 2. The flow
- src/main/java/com/template/TemplateFlow.java
+ cordapp/src/main/java/com/template/TemplateFlow.java
.. code-block:: kotlin
- src/main/kotlin/com/template/App.kt
+ // 1. The state
+ cordapp-contracts-states/src/main/kotlin/com/template/StatesAndContracts.kt
+
+ // 2. The flow
+ cordapp/src/main/kotlin/com/template/App.kt
Clean up
--------
To prevent build errors later on, we should delete the following files before we begin:
-* Java:
- * ``src/main/java/com/template/TemplateClient.java``
- * ``src/test/java/com/template/FlowTests.java``
+* Java: ``cordapp/src/main/java/com/template/TemplateClient.java``
-* Kotlin:
- * ``src/main/kotlin/com/template/TemplateClient.kt``
- * ``src/test/kotlin/com/template/FlowTests.kt``
+* Kotlin: ``cordapp/src/main/kotlin/com/template/TemplateClient.kt``
Progress so far
---------------
diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst
index 34356bad4f..efe8110843 100644
--- a/docs/source/network-map.rst
+++ b/docs/source/network-map.rst
@@ -2,7 +2,7 @@ Network Map
===========
The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact.
-There two sources from which a Corda node can retrieve ``NodeInfo`` objects:
+There are two sources from which a Corda node can retrieve ``NodeInfo`` objects:
1. the REST protocol with the network map service, which also provides a publishing API,
@@ -13,7 +13,7 @@ Protocol Design
---------------
The node info publishing protocol:
-* Create a ``NodeInfo`` object, and sign it to create a ``SignedData`` object. TODO: We will need list of signatures in ``SignedData`` to support multiple node identities in the future.
+* Create a ``NodeInfo`` object, and sign it to create a ``SignedNodeInfo`` object.
* Serialise the signed data and POST the data to the network map server.
@@ -25,7 +25,18 @@ Node side network map update protocol:
* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header.
-* The network map service returns a signed ``NetworkMap`` object, containing list of node info hashes and the network parameters hashes.
+* The network map service returns a signed ``NetworkMap`` object which looks as follows:
+
+.. container:: codeset
+
+ .. sourcecode:: kotlin
+
+ data class NetworkMap {
+ val nodeInfoHashes: List,
+ val networkParametersHash: SecureHash
+ }
+
+The object contains list of node info hashes and hash of the network parameters data structure (without the signatures).
* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``.
@@ -34,13 +45,13 @@ Network Map service REST API:
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| Request method | Path | Description |
+================+===================================+========================================================================================================================================================+
-| POST | /api/network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
+| POST | /network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
-| GET | /api/network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and NetworkParameters hash. |
+| GET | /network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and ``NetworkParameters`` hash. |
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
-| GET | /api/network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
+| GET | /network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
-| GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. |
+| GET | /network-map/parameters/{hash} | Retrieve ``NetworkParameters`` object with the same hash. |
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
TODO: Access control of the network map will be added in the future.
@@ -50,8 +61,24 @@ The ``additional-node-infos`` directory
---------------------------------------
Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
-Nodes expect to find a serialized ``SignedData`` object, the same object which is sent to network map server.
+Nodes expect to find a serialized ``SignedNodeInfo`` object, the same object which is sent to network map server.
Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.
Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory.
+
+
+Network parameters
+------------------
+Network parameters are constants that every node participating in the network needs to agree on and use for interop purposes.
+The structure is distributed as a file containing serialized ``SignedData`` with a signature from
+a sub-key of the compatibility zone root cert. Network map advertises the hash of currently used network parameters.
+The ``NetworkParameters`` structure contains:
+ * ``minimumPlatformVersion`` - minimum version of Corda platform that is required for nodes in the network.
+ * ``notaries`` - list of well known and trusted notary identities with information on validation type.
+ * ``maxMessageSize`` - maximum P2P message size sent over the wire in bytes.
+ * ``maxTransactionSize`` - maximum permitted transaction size in bytes.
+ * ``modifiedTime`` - the time the network parameters were created by the CZ operator.
+ * ``epoch`` - version number of the network parameters. Starting from 1, this will always increment on each new set of parameters.
+
+The set of parameters is still under development and we may find the need to add additional fields.
diff --git a/docs/source/node-auth-config.rst b/docs/source/node-auth-config.rst
deleted file mode 100644
index 5f9c54c3e4..0000000000
--- a/docs/source/node-auth-config.rst
+++ /dev/null
@@ -1,136 +0,0 @@
-Access security settings
-========================
-
-Access to node functionalities via SSH or RPC is protected by an authentication and authorisation policy.
-
-The field ``security`` in ``node.conf`` exposes various sub-fields related to authentication/authorisation specifying:
-
- * The data source providing credentials and permissions for users (e.g.: a remote RDBMS)
- * An optional password encryption method.
- * An optional caching of users data from Node side.
-
-.. warning:: Specifying both ``rpcUsers`` and ``security`` fields in ``node.conf`` is considered an illegal setting and
- rejected by the node at startup since ``rpcUsers`` is effectively deprecated in favour of ``security.authService``.
-
-**Example 1:** connect to remote RDBMS for credentials/permissions, with encrypted user passwords and
-caching on node-side:
-
-.. container:: codeset
-
- .. sourcecode:: groovy
-
- security = {
- authService = {
- dataSource = {
- type = "DB",
- passwordEncryption = "SHIRO_1_CRYPT",
- connection = {
- jdbcUrl = ""
- username = ""
- password = ""
- driverClassName = ""
- }
- }
- options = {
- cache = {
- expiryTimeSecs = 120
- capacity = 10000
- }
- }
- }
- }
-
-**Example 2:** list of user credentials and permissions hard-coded in ``node.conf``
-
-.. container:: codeset
-
- .. sourcecode:: groovy
-
- security = {
- authService = {
- dataSource = {
- type = "INMEMORY",
- users =[
- {
- username = "user1"
- password = "password"
- permissions = [
- "StartFlow.net.corda.flows.ExampleFlow1",
- "StartFlow.net.corda.flows.ExampleFlow2",
- ...
- ]
- },
- ...
- ]
- }
- }
- }
-
-Let us look in more details at the structure of ``security.authService``:
-
-Authentication/authorisation data
----------------------------------
-
-The ``dataSource`` field defines the data provider supplying credentials and permissions for users. The ``type``
-subfield identify the type of data provider, currently supported one are:
-
- * **INMEMORY:** a list of user credentials and permissions hard-coded in configuration in the ``users`` field
- (see example 2 above)
-
- * **DB:** An external RDBMS accessed via the JDBC connection described by ``connection``. The current implementation
- expect the database to store data according to the following schema:
-
- - Table ``users`` containing columns ``username`` and ``password``.
- The ``username`` column *must have unique values*.
- - Table ``user_roles`` containing columns ``username`` and ``role_name`` associating a user to a set of *roles*
- - Table ``roles_permissions`` containing columns ``role_name`` and ``permission`` associating a role to a set of
- permission strings
-
- Note in particular how in the DB case permissions are assigned to _roles_ rather than individual users.
- Also, there is no prescription on the SQL type of the columns (although in our tests we defined ``username`` and
- ``role_name`` of SQL type ``VARCHAR`` and ``password`` of ``TEXT`` type) and it is allowed to put additional columns
- besides the one expected by the implementation.
-
-Password encryption
--------------------
-
-Storing passwords in plain text is discouraged in production systems aiming for high security requirements. We support
-reading passwords stored using the Apache Shiro fully reversible Modular Crypt Format, specified in the documentation
-of ``org.apache.shiro.crypto.hash.format.Shiro1CryptFormat``.
-
-Password are assumed in plain format by default. To specify an encryption it is necessary to use the field:
-
-.. container:: codeset
-
- .. sourcecode:: groovy
-
- passwordEncryption = SHIRO_1_CRYPT
-
-Hash encrypted password based on the Shiro1CryptFormat can be produced with the `Apache Shiro Hasher tool `_
-
-Cache
------
-
-Adding a cache layer on top of an external provider of users credentials and permissions can significantly benefit
-performances in some cases, with the disadvantage of introducing a latency in the propagation of changes to the data.
-
-Caching of users data is disabled by default, it can be enabled by defining the ``options.cache`` field, like seen in
-the examples above:
-
-.. container:: codeset
-
- .. sourcecode:: groovy
-
- options = {
- cache = {
- expiryTimeSecs = 120
- capacity = 10000
- }
- }
-
-This will enable an in-memory cache with maximum capacity (number of entries) and maximum life time of entries given by
-respectively the values set by the ``capacity`` and ``expiryTimeSecs`` fields.
-
-
-
-
diff --git a/docs/source/permissioning.rst b/docs/source/permissioning.rst
index dcffd0bb49..5532157237 100644
--- a/docs/source/permissioning.rst
+++ b/docs/source/permissioning.rst
@@ -32,7 +32,13 @@ A Corda network has three types of certificate authorities (CAs):
* The **node CAs**
* Each node serves as its own CA in issuing the child certificates that it uses to sign its identity
- keys, anonymous keys and TLS certificates
+ keys and TLS certificates
+
+We can visualise the permissioning structure as follows:
+
+.. image:: resources/certificate_structure.png
+ :scale: 55%
+ :align: center
Keypair and certificate formats
-------------------------------
@@ -45,6 +51,13 @@ public/private keypairs and certificates. The keypairs and certificates should o
* The TLS certificates must follow the `TLS v1.2 standard `_
+* The root network CA, intermediate network CA and node CA keys, as well as the node TLS
+ keys, must follow one of the following schemes:
+
+ * ECDSA using the NIST P-256 curve (secp256r1)
+
+ * RSA with 3072-bit key size
+
Creating the root and intermediate network CAs
----------------------------------------------
diff --git a/docs/source/release-process-index.rst b/docs/source/release-process-index.rst
index eb81e833f0..1b59977668 100644
--- a/docs/source/release-process-index.rst
+++ b/docs/source/release-process-index.rst
@@ -6,6 +6,5 @@ Release process
release-notes
changelog
- upgrade-notes
codestyle
testing
\ No newline at end of file
diff --git a/docs/source/resources/certificate_structure.png b/docs/source/resources/certificate_structure.png
new file mode 100644
index 0000000000..6121b968da
Binary files /dev/null and b/docs/source/resources/certificate_structure.png differ
diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst
index 4b610ef104..4ae763dce2 100644
--- a/docs/source/setting-up-a-corda-network.rst
+++ b/docs/source/setting-up-a-corda-network.rst
@@ -6,11 +6,9 @@ Creating a Corda network
A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in
order to create and validate transactions.
-There are four broader categories of functionality one such node may have. These pieces of functionality are provided
+There are three broader categories of functionality one such node may have. These pieces of functionality are provided
as services, and one node may run several of them.
-* Network map: The node running the network map provides a way to resolve identities to physical node addresses and
- associated public keys
* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a
double-spend or not
* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of
@@ -46,12 +44,38 @@ The most important fields regarding network configuration are:
* ``rpcAddress``: The address to which Artemis will bind for RPC calls.
* ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` and ``rpcAddress`` if they are on the same machine.
+Bootstrapping the network
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The nodes see each other using the network map. This is a collection of statically signed node-info files, one for each
+node in the network. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
+
+For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
+placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
+in its local network map cache. The node generates its own node-info file on startup.
+
+In addition to the network map, all the nodes on a network must use the same set of network parameters. These are a set
+of constants which guarantee interoperability between nodes. The HTTP network map distributes the network parameters
+which the node downloads automatically. In the absence of this the network parameters must be generated locally. This can
+be done with the network bootstrapper. This a tool that scans all the node configurations from a common directory to
+generate the network parameters file which is copied to the nodes' directories. It also copies each node's node-info file
+to every other node.
+
+The bootstrapper tool can be built with the command:
+
+``gradlew buildBootstrapperJar``
+
+The resulting jar can be found in ``tools/bootstrapper/build/libs/``.
+
+To use it, run the following command, specifying the root directory which hosts all the node directories as the argument:
+
+``java -jar network-bootstrapper.jar ``
+
Starting the nodes
~~~~~~~~~~~~~~~~~~
-You may now start the nodes in any order. Note that the node is not fully started until it has successfully registered with the network map!
-
-You should see a banner, some log lines and eventually ``Node started up and registered``, indicating that the node is fully started.
+You may now start the nodes in any order. You should see a banner, some log lines and eventually ``Node started up and registered``,
+indicating that the node is fully started.
.. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound.
@@ -66,7 +90,6 @@ details/diagnosing problems check the logs.
Logging is standard log4j2_ and may be configured accordingly. Logs
are by default redirected to files in ``NODE_DIRECTORY/logs/``.
-
Connecting to the nodes
~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/source/tut-two-party-contract.rst b/docs/source/tut-two-party-contract.rst
index 2011f73c55..17e4bc77f2 100644
--- a/docs/source/tut-two-party-contract.rst
+++ b/docs/source/tut-two-party-contract.rst
@@ -77,7 +77,7 @@ We can picture this transaction as follows:
Defining IOUContract
--------------------
Let's write a contract that enforces these constraints. We'll do this by modifying either ``TemplateContract.java`` or
-``App.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
+``StatesAndContracts.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
.. container:: codeset
diff --git a/docs/source/tut-two-party-flow.rst b/docs/source/tut-two-party-flow.rst
index 84f373047c..4b6e1c3cda 100644
--- a/docs/source/tut-two-party-flow.rst
+++ b/docs/source/tut-two-party-flow.rst
@@ -17,7 +17,7 @@ We'll do this by modifying the flow we wrote in the previous tutorial.
Verifying the transaction
-------------------------
-In ``IOUFlow.java``/``IOUFlow.kt``, change the imports block to the following:
+In ``IOUFlow.java``/``App.kt``, change the imports block to the following:
.. container:: codeset
@@ -31,7 +31,8 @@ In ``IOUFlow.java``/``IOUFlow.kt``, change the imports block to the following:
:start-after: DOCSTART 01
:end-before: DOCEND 01
-And update ``IOUFlow.call`` by changing the code following the creation of the ``TransactionBuilder`` as follows:
+And update ``IOUFlow.call`` by changing the code following the retrieval of the notary's identity from the network as
+follows:
.. container:: codeset
@@ -138,6 +139,11 @@ Our CorDapp now imposes restrictions on the issuance of IOUs. Most importantly,
from both the lender and the borrower before an IOU can be created on the ledger. This prevents either the lender or
the borrower from unilaterally updating the ledger in a way that only benefits themselves.
+After completing this tutorial, your CorDapp should look like this:
+
+* Java: https://github.com/corda/corda-tut2-solution-java
+* Kotlin: https://github.com/corda/corda-tut2-solution-kotlin
+
You should now be ready to develop your own CorDapps. You can also find a list of sample CorDapps
`here `_. As you write CorDapps, you'll also want to learn more about the
:doc:`Corda API `.
diff --git a/docs/source/tutorials-index.rst b/docs/source/tutorials-index.rst
index c443de09b0..dab76fb001 100644
--- a/docs/source/tutorials-index.rst
+++ b/docs/source/tutorials-index.rst
@@ -1,11 +1,23 @@
Tutorials
=========
+This section is split into two parts.
+
+The Hello, World tutorials should be followed in sequence and show how to extend the Java or Kotlin CorDapp Template
+into a full CorDapp.
+
.. toctree::
:maxdepth: 1
hello-world-introduction
tut-two-party-introduction
+
+The remaining tutorials cover individual platform features in isolation. They don't depend on the code from the Hello,
+World tutorials, and can be read in any order.
+
+.. toctree::
+ :maxdepth: 1
+
tutorial-contract
tutorial-test-dsl
contract-upgrade
diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst
index 7ddbd362f1..baa2328b4e 100644
--- a/docs/source/upgrade-notes.rst
+++ b/docs/source/upgrade-notes.rst
@@ -1,5 +1,5 @@
-Upgrade notes
-=============
+Upgrading a CorDapp to a new version
+====================================
These notes provide instructions for upgrading your CorDapps from previous versions, starting with the upgrade from our
first public Beta (:ref:`Milestone 12 `), to :ref:`V1.0 `.
diff --git a/docs/source/versioning.rst b/docs/source/versioning.rst
index f53e9fb852..b860f71507 100644
--- a/docs/source/versioning.rst
+++ b/docs/source/versioning.rst
@@ -32,8 +32,8 @@ Flow versioning
---------------
In addition to the evolution of the platform, flows that run on top of the platform can also evolve. It may be that the
-flow protocol between an initiating flow and it's intiated flow changes from one CorDapp release to the next in such as
-way to be backwards incompatible with existing flows. For example, if a sequence of sends and receives needs to change
+flow protocol between an initiating flow and its initiated flow changes from one CorDapp release to the next in such a
+way to be backward incompatible with existing flows. For example, if a sequence of sends and receives needs to change
or if the semantics of a particular receive changes.
The ``InitiatingFlow`` annotation (see :doc:`flow-state-machine` for more information on the flow annotations) has a ``version``
diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt
index bb458188e6..ed12e6190c 100644
--- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt
+++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt
@@ -9,6 +9,10 @@ import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.Tenor
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
+import net.corda.testing.dsl.EnforceVerifyOrFail
+import net.corda.testing.dsl.TransactionDSL
+import net.corda.testing.dsl.TransactionDSLInterpreter
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.transaction
import org.junit.Ignore
diff --git a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java
index ce7273f75b..5d3f27286a 100644
--- a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java
+++ b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java
@@ -12,51 +12,52 @@ import net.corda.testing.node.MockServices;
import org.junit.Rule;
import org.junit.Test;
+import static java.util.Collections.emptyList;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.Currencies.issuedBy;
import static net.corda.testing.node.NodeTestUtils.transaction;
-import static net.corda.testing.CoreTestUtils.rigorousMock;
-import static net.corda.testing.TestConstants.getDUMMY_NOTARY_NAME;
+import static net.corda.testing.internal.InternalTestUtilsKt.rigorousMock;
+import static net.corda.testing.TestConstants.DUMMY_NOTARY_NAME;
import static org.mockito.Mockito.doReturn;
/**
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
*/
public class CashTestsJava {
- private static final Party DUMMY_NOTARY = new TestIdentity(getDUMMY_NOTARY_NAME(), 20L).getParty();
+ private static final Party DUMMY_NOTARY = new TestIdentity(DUMMY_NOTARY_NAME, 20L).getParty();
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
private static final TestIdentity MINI_CORP = new TestIdentity(new CordaX500Name("MiniCorp", "London", "GB"));
private final PartyAndReference defaultIssuer = MEGA_CORP.ref((byte) 1);
- private final Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), new AnonymousParty(MEGA_CORP.getPubkey()));
- private final Cash.State outState = new Cash.State(inState.getAmount(), new AnonymousParty(MINI_CORP.getPubkey()));
+ private final Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), new AnonymousParty(MEGA_CORP.getPublicKey()));
+ private final Cash.State outState = new Cash.State(inState.getAmount(), new AnonymousParty(MINI_CORP.getPublicKey()));
@Rule
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
@Test
public void trivial() {
IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
- doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPubkey());
- doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPubkey());
- transaction(new MockServices(identityService, MEGA_CORP.getName()), DUMMY_NOTARY, tx -> {
+ doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPublicKey());
+ doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPublicKey());
+ transaction(new MockServices(emptyList(), identityService, MEGA_CORP.getName()), DUMMY_NOTARY, tx -> {
tx.attachment(Cash.PROGRAM_ID);
tx.input(Cash.PROGRAM_ID, inState);
tx.tweak(tw -> {
- tw.output(Cash.PROGRAM_ID, new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), new AnonymousParty(MINI_CORP.getPubkey())));
- tw.command(MEGA_CORP.getPubkey(), new Cash.Commands.Move());
+ tw.output(Cash.PROGRAM_ID, new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), new AnonymousParty(MINI_CORP.getPublicKey())));
+ tw.command(MEGA_CORP.getPublicKey(), new Cash.Commands.Move());
return tw.failsWith("the amounts balance");
});
tx.tweak(tw -> {
tw.output(Cash.PROGRAM_ID, outState);
- tw.command(MEGA_CORP.getPubkey(), DummyCommandData.INSTANCE);
+ tw.command(MEGA_CORP.getPublicKey(), DummyCommandData.INSTANCE);
// Invalid command
return tw.failsWith("required net.corda.finance.contracts.asset.Cash.Commands.Move command");
});
tx.tweak(tw -> {
tw.output(Cash.PROGRAM_ID, outState);
- tw.command(MINI_CORP.getPubkey(), new Cash.Commands.Move());
+ tw.command(MINI_CORP.getPublicKey(), new Cash.Commands.Move());
return tw.failsWith("the owning keys are a subset of the signing keys");
});
tx.tweak(tw -> {
@@ -64,14 +65,14 @@ public class CashTestsJava {
// issuedBy() can't be directly imported because it conflicts with other identically named functions
// with different overloads (for some reason).
tw.output(Cash.PROGRAM_ID, outState.issuedBy(MINI_CORP.getParty()));
- tw.command(MEGA_CORP.getPubkey(), new Cash.Commands.Move());
+ tw.command(MEGA_CORP.getPublicKey(), new Cash.Commands.Move());
return tw.failsWith("at least one cash input");
});
// Simple reallocation works.
return tx.tweak(tw -> {
tw.output(Cash.PROGRAM_ID, outState);
- tw.command(MEGA_CORP.getPubkey(), new Cash.Commands.Move());
+ tw.command(MEGA_CORP.getPublicKey(), new Cash.Commands.Move());
return tw.verifies();
});
});
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 81f40d5c6f..6b28671413 100644
--- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
+++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
@@ -18,7 +18,11 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.*
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
-import net.corda.testing.contracts.VaultFiller
+import net.corda.testing.dsl.EnforceVerifyOrFail
+import net.corda.testing.dsl.TransactionDSL
+import net.corda.testing.dsl.TransactionDSLInterpreter
+import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.ledger
@@ -46,7 +50,7 @@ interface ICommercialPaperTestTemplate {
private val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
private val MEGA_CORP get() = megaCorp.party
private val MEGA_CORP_IDENTITY get() = megaCorp.identity
-private val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+private val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
class JavaCommercialPaperTest : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
@@ -105,13 +109,13 @@ class CommercialPaperTestsGeneric {
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
private val ALICE get() = alice.party
- private val ALICE_KEY get() = alice.key
- private val ALICE_PUBKEY get() = alice.pubkey
+ private val ALICE_KEY get() = alice.keyPair
+ private val ALICE_PUBKEY get() = alice.publicKey
private val DUMMY_NOTARY get() = dummyNotary.party
private val DUMMY_NOTARY_IDENTITY get() = dummyNotary.identity
private val MINI_CORP get() = miniCorp.party
private val MINI_CORP_IDENTITY get() = miniCorp.identity
- private val MINI_CORP_PUBKEY get() = miniCorp.pubkey
+ private val MINI_CORP_PUBKEY get() = miniCorp.publicKey
}
@Parameterized.Parameter
@@ -120,7 +124,7 @@ class CommercialPaperTestsGeneric {
@JvmField
val testSerialization = SerializationEnvironmentRule()
val issuer = MEGA_CORP.ref(123)
- private val ledgerServices = MockServices(rigorousMock().also {
+ private val ledgerServices = MockServices(emptyList(), rigorousMock().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
@@ -260,16 +264,15 @@ class CommercialPaperTestsGeneric {
private lateinit var aliceServices: MockServices
private lateinit var aliceVaultService: VaultService
private lateinit var alicesVault: Vault
- private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, dummyNotary.key)
- private val issuerServices = MockServices(listOf("net.corda.finance.contracts", "net.corda.finance.schemas"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.key)
+ private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair)
+ private val issuerServices = MockServices(listOf("net.corda.finance.contracts", "net.corda.finance.schemas"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.keyPair)
private lateinit var moveTX: SignedTransaction
@Test
fun `issue move and then redeem`() {
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(
- listOf(ALICE_KEY),
- makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
- listOf("net.corda.finance.contracts", "net.corda.finance.schemas"),
- MEGA_CORP.name)
+ listOf("net.corda.finance.contracts"),
+ makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
+ TestIdentity(MEGA_CORP.name, ALICE_KEY))
val databaseAlice = aliceDatabaseAndServices.first
aliceServices = aliceDatabaseAndServices.second
aliceVaultService = aliceServices.vaultService
@@ -279,10 +282,9 @@ class CommercialPaperTestsGeneric {
aliceVaultService = aliceServices.vaultService
}
val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(
- listOf(BIG_CORP_KEY),
- makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
- listOf("net.corda.finance.contracts", "net.corda.finance.schemas"),
- MEGA_CORP.name)
+ listOf("net.corda.finance.contracts"),
+ makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
+ TestIdentity(MEGA_CORP.name, BIG_CORP_KEY))
val databaseBigCorp = bigCorpDatabaseAndServices.first
bigCorpServices = bigCorpDatabaseAndServices.second
bigCorpVaultService = bigCorpServices.vaultService
@@ -308,8 +310,8 @@ class CommercialPaperTestsGeneric {
// Alice pays $9000 to BigCorp to own some of their debt.
moveTX = run {
val builder = TransactionBuilder(DUMMY_NOTARY)
- Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public))
- CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(aliceServices.key.public))
+ Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(BIG_CORP_KEY.public))
+ CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(ALICE_KEY.public))
val ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = bigCorpServices.addSignature(ptx)
val stx = notaryServices.addSignature(ptx2)
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 66ed294997..feb9032fab 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
@@ -3,7 +3,6 @@ package net.corda.finance.contracts.asset
import com.nhaarman.mockito_kotlin.*
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
-import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.*
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.VaultService
@@ -21,7 +20,12 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.*
import net.corda.testing.contracts.DummyState
-import net.corda.testing.contracts.VaultFiller
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.dsl.EnforceVerifyOrFail
+import net.corda.testing.dsl.TransactionDSL
+import net.corda.testing.dsl.TransactionDSLInterpreter
+import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.ledger
@@ -37,27 +41,27 @@ import kotlin.test.*
class CashTests {
private companion object {
val alice = TestIdentity(ALICE_NAME, 70)
- val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).pubkey
+ val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).publicKey
val charlie = TestIdentity(CHARLIE_NAME, 90)
val DUMMY_CASH_ISSUER_IDENTITY = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10).identity
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val ALICE get() = alice.party
- val ALICE_PUBKEY get() = alice.pubkey
+ val ALICE_PUBKEY get() = alice.publicKey
val CHARLIE get() = charlie.party
val CHARLIE_IDENTITY get() = charlie.identity
val DUMMY_NOTARY get() = dummyNotary.party
val DUMMY_NOTARY_IDENTITY get() = dummyNotary.identity
- val DUMMY_NOTARY_KEY get() = dummyNotary.key
+ val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
val MEGA_CORP get() = megaCorp.party
val MEGA_CORP_IDENTITY get() = megaCorp.identity
- val MEGA_CORP_KEY get() = megaCorp.key
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+ val MEGA_CORP_KEY get() = megaCorp.keyPair
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
val MINI_CORP_IDENTITY get() = miniCorp.identity
- val MINI_CORP_KEY get() = miniCorp.key
- val MINI_CORP_PUBKEY get() = miniCorp.pubkey
+ val MINI_CORP_KEY get() = miniCorp.keyPair
+ val MINI_CORP_PUBKEY get() = miniCorp.publicKey
}
@Rule
@@ -99,10 +103,9 @@ class CashTests {
}, MINI_CORP.name, MINI_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices(
- listOf(generateKeyPair()),
- makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
- listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
- CordaX500Name("Me", "London", "GB"))
+ listOf("net.corda.finance.contracts.asset"),
+ makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
+ TestIdentity(CordaX500Name("Me", "London", "GB")))
database = databaseAndServices.first
ourServices = databaseAndServices.second
@@ -138,7 +141,7 @@ class CashTests {
}
private fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run {
- MockServices(rigorousMock().also {
+ MockServices(emptyList(), rigorousMock().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
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 2670b0674f..73f1363d36 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
@@ -22,6 +22,8 @@ import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
+import net.corda.testing.dsl.*
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import net.corda.testing.node.transaction
@@ -45,14 +47,14 @@ class ObligationTests {
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val ALICE get() = alice.party
- val ALICE_PUBKEY get() = alice.pubkey
+ val ALICE_PUBKEY get() = alice.publicKey
val BOB get() = bob.party
- val BOB_PUBKEY get() = bob.pubkey
+ val BOB_PUBKEY get() = bob.publicKey
val DUMMY_NOTARY get() = dummyNotary.party
val MEGA_CORP get() = megaCorp.party
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
- val MINI_CORP_PUBKEY get() = miniCorp.pubkey
+ val MINI_CORP_PUBKEY get() = miniCorp.publicKey
}
@Rule
@@ -77,7 +79,7 @@ class ObligationTests {
)
private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY))
private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), miniCorp)
- private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, dummyNotary.key)
+ private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair)
private val identityService = rigorousMock().also {
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
@@ -86,7 +88,7 @@ class ObligationTests {
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
}
private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), identityService, MEGA_CORP.name)
- private val ledgerServices get() = MockServices(identityService, MEGA_CORP.name)
+ private val ledgerServices get() = MockServices(emptyList(), identityService, MEGA_CORP.name)
private fun cashObligationTestRoots(
group: LedgerDSL
) = group.apply {
diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt
index cffc411f7f..dc131cc8b5 100644
--- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt
+++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt
@@ -2,18 +2,16 @@ package net.corda.plugins
import groovy.lang.Closure
import net.corda.cordform.CordformDefinition
-import net.corda.cordform.CordformNode
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.DefaultTask
-import org.gradle.api.GradleException
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import org.gradle.api.tasks.TaskAction
import java.io.File
+import java.lang.reflect.InvocationTargetException
import java.net.URLClassLoader
import java.nio.file.Path
import java.nio.file.Paths
-import java.util.concurrent.TimeUnit
import java.util.jar.JarInputStream
/**
@@ -118,13 +116,13 @@ open class Cordform : DefaultTask() {
}
/**
- * The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
+ * The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
- private fun loadNetworkParamsGenClass(): Class<*> {
+ private fun loadNetworkBootstrapperClass(): Class<*> {
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
- return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.NetworkParametersGenerator")
+ return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
}
/**
@@ -136,8 +134,7 @@ open class Cordform : DefaultTask() {
initializeConfiguration()
installRunScript()
nodes.forEach(Node::build)
- generateAndInstallNodeInfos()
- generateAndInstallNetworkParameters()
+ bootstrapNetwork()
}
private fun initializeConfiguration() {
@@ -164,14 +161,17 @@ open class Cordform : DefaultTask() {
}
}
- private fun generateAndInstallNetworkParameters() {
- project.logger.info("Generating and installing network parameters")
- val networkParamsGenClass = loadNetworkParamsGenClass()
- val nodeDirs = nodes.map(Node::fullPath)
- val networkParamsGenObject = networkParamsGenClass.newInstance()
- val runMethod = networkParamsGenClass.getMethod("run", List::class.java).apply { isAccessible = true }
- // Call NetworkParametersGenerator.run
- runMethod.invoke(networkParamsGenObject, nodeDirs)
+ private fun bootstrapNetwork() {
+ val networkBootstrapperClass = loadNetworkBootstrapperClass()
+ val networkBootstrapper = networkBootstrapperClass.newInstance()
+ val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
+ // Call NetworkBootstrapper.bootstrap
+ try {
+ val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
+ bootstrapMethod.invoke(networkBootstrapper, rootDir)
+ } catch (e: InvocationTargetException) {
+ throw e.cause!!
+ }
}
private fun CordformDefinition.getMatchingCordapps(): List {
@@ -197,90 +197,4 @@ open class Cordform : DefaultTask() {
return false
}
}
-
- private fun generateAndInstallNodeInfos() {
- generateNodeInfos()
- installNodeInfos()
- }
-
- private fun generateNodeInfos() {
- project.logger.info("Generating node infos")
- val nodeProcesses = buildNodeProcesses()
- try {
- validateNodeProcessess(nodeProcesses)
- } finally {
- destroyNodeProcesses(nodeProcesses)
- }
- }
-
- private fun buildNodeProcesses(): Map {
- val command = generateNodeInfoCommand()
- return nodes.map {
- it.makeLogDirectory()
- buildProcess(it, command, "generate-info.log") }.toMap()
- }
-
- private fun validateNodeProcessess(nodeProcesses: Map) {
- nodeProcesses.forEach { (node, process) ->
- validateNodeProcess(node, process)
- }
- }
-
- private fun destroyNodeProcesses(nodeProcesses: Map) {
- nodeProcesses.forEach { (_, process) ->
- process.destroyForcibly()
- }
- }
-
- private fun buildProcess(node: Node, command: List, logFile: String): Pair {
- val process = ProcessBuilder(command)
- .directory(node.fullPath().toFile())
- .redirectErrorStream(true)
- // InheritIO causes hangs on windows due the gradle buffer also not being flushed.
- // Must redirect to output or logger (node log is still written, this is just startup banner)
- .redirectOutput(node.logFile(logFile).toFile())
- .addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir)
- .start()
- return Pair(node, process)
- }
-
- private fun generateNodeInfoCommand(): List = listOf(
- "java",
- "-Dcapsule.log=verbose",
- "-Dcapsule.dir=${Node.capsuleCacheDir}",
- "-jar",
- Node.nodeJarName,
- "--just-generate-node-info"
- )
-
- private fun validateNodeProcess(node: Node, process: Process) {
- val generateTimeoutSeconds = 60L
- if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) {
- throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${node.fullPath()}/logs")
- }
- if (process.exitValue() != 0) {
- throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${node.fullPath()}/logs")
- }
- project.logger.info("Generated node info for ${node.fullPath()}")
- }
-
- private fun installNodeInfos() {
- project.logger.info("Installing node infos")
- for (source in nodes) {
- for (destination in nodes) {
- if (source.nodeDir != destination.nodeDir) {
- project.copy {
- it.apply {
- from(source.fullPath().toString())
- include("nodeInfo-*")
- into(destination.fullPath().resolve(CordformNode.NODE_INFO_DIRECTORY).toString())
- }
- }
- }
- }
- }
- }
-
- private fun Node.logFile(name: String): Path = this.logDirectory().resolve(name)
- private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) }
}
diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt
index 89f425dd60..ffaab5c5b1 100644
--- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt
+++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt
@@ -20,13 +20,8 @@ class Node(private val project: Project) : CordformNode() {
@JvmStatic
val webJarName = "corda-webserver.jar"
private val configFileProperty = "configFile"
- val capsuleCacheDir: String = "./cache"
}
- fun fullPath(): Path = project.projectDir.toPath().resolve(nodeDir.toPath())
- fun logDirectory(): Path = fullPath().resolve("logs")
- fun makeLogDirectory() = Files.createDirectories(logDirectory())
-
/**
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
* dependency name, eg: com.example:product-name:0.1
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 94953584ab..c49bd847f7 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
@@ -122,7 +122,7 @@ private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: I
end tell""")
}
OS.WINDOWS -> {
- listOf("cmd", "/C", "start ${command.joinToString(" ")}")
+ listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}")
}
OS.LINUX -> {
// Start shell to keep window open unless java terminated normally or due to SIGTERM:
@@ -136,12 +136,11 @@ end tell""")
})
private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ")
- override fun getJavaPath(): String {
- val path = File(File(System.getProperty("java.home"), "bin"), "java").path
- // Replace below is to fix an issue with spaces in paths on Windows.
- // Quoting the entire path does not work, only the space or directory within the path.
- return if (os == OS.WINDOWS) path.replace(" ", "\" \"") else path
- }
+ override fun getJavaPath(): String = File(File(System.getProperty("java.home"), "bin"), "java").path
+
+ // Replace below is to fix an issue with spaces in paths on Windows.
+ // Quoting the entire path does not work, only the space or directory within the path.
+ private fun windowsSpaceEscape(s:String) = s.replace(" ", "\" \"")
}
private fun quotedFormOf(text: String) = "'${text.replace("'", "'\\''")}'" // Suitable for UNIX shells.
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt
index ab1720934a..9811800de0 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt
@@ -29,12 +29,14 @@ class ArtemisTcpTransport {
// but we allow classical RSA certificates to work in case:
// a) we need to use keytool certificates in some demos,
// b) we use cloud providers or HSMs that do not support ECC.
- private val CIPHER_SUITES = listOf(
+ val CIPHER_SUITES = listOf(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"
)
+ val TLS_VERSIONS = listOf("TLSv1.2")
+
fun tcpTransport(
direction: ConnectionDirection,
hostAndPort: NetworkHostAndPort,
@@ -68,7 +70,7 @@ class ArtemisTcpTransport {
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to config.trustStoreFile,
TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to config.trustStorePassword,
TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME to CIPHER_SUITES.joinToString(","),
- TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to "TLSv1.2",
+ TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to TLS_VERSIONS.joinToString(","),
TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true,
VERIFY_PEER_LEGAL_NAME to (direction as? ConnectionDirection.Outbound)?.expectedCommonNames
)
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt
deleted file mode 100644
index d464575a4f..0000000000
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package net.corda.nodeapi.internal
-
-import net.corda.core.crypto.SignedData
-import net.corda.core.crypto.entropyToKeyPair
-import net.corda.core.crypto.sign
-import net.corda.core.internal.copyTo
-import net.corda.core.internal.div
-import net.corda.core.serialization.serialize
-import net.corda.nodeapi.internal.NetworkParameters
-import java.math.BigInteger
-import java.nio.file.FileAlreadyExistsException
-import java.nio.file.Path
-
-class NetworkParametersCopier(networkParameters: NetworkParameters) {
- private companion object {
- val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123))
- }
-
- private val serializedNetworkParameters = networkParameters.let {
- val serialize = it.serialize()
- val signature = DUMMY_MAP_KEY.sign(serialize)
- SignedData(serialize, signature).serialize()
- }
-
- fun install(dir: Path) {
- try {
- serializedNetworkParameters.open().copyTo(dir / "network-parameters")
- } catch (e: FileAlreadyExistsException) {
- // Leave the file untouched if it already exists
- }
- }
-}
\ No newline at end of file
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt
deleted file mode 100644
index 185e8bda8f..0000000000
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-package net.corda.nodeapi.internal
-
-import com.typesafe.config.ConfigFactory
-import net.corda.core.crypto.SignedData
-import net.corda.core.identity.Party
-import net.corda.core.internal.div
-import net.corda.core.internal.list
-import net.corda.core.internal.readAll
-import net.corda.core.node.NodeInfo
-import net.corda.core.serialization.SerializationContext
-import net.corda.core.serialization.deserialize
-import net.corda.core.serialization.internal.SerializationEnvironmentImpl
-import net.corda.core.serialization.internal._contextSerializationEnv
-import net.corda.core.utilities.ByteSequence
-import net.corda.core.utilities.contextLogger
-import net.corda.core.utilities.days
-import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
-import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
-import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
-import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
-import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
-import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
-import java.nio.file.Path
-import java.time.Instant
-
-/**
- * This class is loaded by Cordform using reflection to generate the network parameters. It is assumed that Cordform has
- * already asked each node to generate its node info file.
- */
-@Suppress("UNUSED")
-class NetworkParametersGenerator {
- companion object {
- private val logger = contextLogger()
- }
-
- fun run(nodesDirs: List) {
- logger.info("NetworkParameters generation using node directories: $nodesDirs")
- try {
- initialiseSerialization()
- val notaryInfos = gatherNotaryIdentities(nodesDirs)
- val copier = NetworkParametersCopier(NetworkParameters(
- minimumPlatformVersion = 1,
- notaries = notaryInfos,
- modifiedTime = Instant.now(),
- eventHorizon = 10000.days,
- maxMessageSize = 40000,
- maxTransactionSize = 40000,
- epoch = 1
- ))
- nodesDirs.forEach(copier::install)
- } finally {
- _contextSerializationEnv.set(null)
- }
- }
-
- private fun gatherNotaryIdentities(nodesDirs: List): List {
- return nodesDirs.mapNotNull { nodeDir ->
- val nodeConfig = ConfigFactory.parseFile((nodeDir / "node.conf").toFile())
- if (nodeConfig.hasPath("notary")) {
- val validating = nodeConfig.getConfig("notary").getBoolean("validating")
- val nodeInfoFile = nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
- processFile(nodeInfoFile)?.let { NotaryInfo(it.notaryIdentity(), validating) }
- } else {
- null
- }
- }.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
- }
-
- private fun NodeInfo.notaryIdentity(): Party {
- return when (legalIdentities.size) {
- // Single node notaries have just one identity like all other nodes. This identity is the notary identity
- 1 -> legalIdentities[0]
- // Nodes which are part of a distributed notary have a second identity which is the composite identity of the
- // cluster and is shared by all the other members. This is the notary identity.
- 2 -> legalIdentities[1]
- else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
- }
- }
-
- private fun processFile(file: Path): NodeInfo? {
- return try {
- logger.info("Reading NodeInfo from file: $file")
- val signedData = file.readAll().deserialize>()
- signedData.verified()
- } catch (e: Exception) {
- logger.warn("Exception parsing NodeInfo from file. $file", e)
- null
- }
- }
-
- // We need to to set serialization env, because generation of parameters is run from Cordform.
- // KryoServerSerializationScheme is not accessible from nodeapi.
- private fun initialiseSerialization() {
- val context = if (java.lang.Boolean.getBoolean("net.corda.testing.amqp.enable")) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT
- _contextSerializationEnv.set(SerializationEnvironmentImpl(
- SerializationFactoryImpl().apply {
- registerScheme(KryoParametersSerializationScheme)
- registerScheme(AMQPServerSerializationScheme())
- },
- context)
- )
- }
-
- private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
- override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
- return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
- }
- override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
- override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
- }
-}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt
new file mode 100644
index 0000000000..1b0ed15348
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt
@@ -0,0 +1,44 @@
+package net.corda.nodeapi.internal
+
+import net.corda.core.crypto.CompositeKey
+import net.corda.core.crypto.DigitalSignature
+import net.corda.core.crypto.verify
+import net.corda.core.node.NodeInfo
+import net.corda.core.serialization.CordaSerializable
+import net.corda.core.serialization.SerializedBytes
+import net.corda.core.serialization.deserialize
+import java.security.SignatureException
+
+/**
+ * A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
+ * to be in the same order as the identities.
+ */
+// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
+// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
+// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite
+// public keys.
+@CordaSerializable
+class SignedNodeInfo(val raw: SerializedBytes, val signatures: List) {
+ fun verified(): NodeInfo {
+ val nodeInfo = raw.deserialize()
+ val identities = nodeInfo.legalIdentities.filterNot { it.owningKey is CompositeKey }
+
+ if (identities.size < signatures.size) {
+ throw SignatureException("Extra signatures. Found ${signatures.size} expected ${identities.size}")
+ }
+ if (identities.size > signatures.size) {
+ throw SignatureException("Missing signatures. Found ${signatures.size} expected ${identities.size}")
+ }
+
+ val rawBytes = raw.bytes // To avoid cloning the byte array multiple times
+ identities.zip(signatures).forEach { (identity, signature) ->
+ try {
+ identity.owningKey.verify(rawBytes, signature)
+ } catch (e: SignatureException) {
+ throw SignatureException("$identity: ${e.message}")
+ }
+ }
+
+ return nodeInfo
+ }
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
new file mode 100644
index 0000000000..34ee05851a
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
@@ -0,0 +1,167 @@
+package net.corda.nodeapi.internal.network
+
+import com.typesafe.config.ConfigFactory
+import net.corda.cordform.CordformNode
+import net.corda.core.identity.Party
+import net.corda.core.internal.*
+import net.corda.core.node.NodeInfo
+import net.corda.core.serialization.SerializationContext
+import net.corda.core.serialization.deserialize
+import net.corda.core.serialization.internal.SerializationEnvironmentImpl
+import net.corda.core.serialization.internal._contextSerializationEnv
+import net.corda.core.utilities.ByteSequence
+import net.corda.nodeapi.internal.SignedNodeInfo
+import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
+import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
+import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
+import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
+import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.StandardCopyOption
+import java.time.Instant
+import java.util.concurrent.TimeUnit.SECONDS
+import kotlin.streams.toList
+
+/**
+ * Class to bootstrap a local network of Corda nodes on the same filesystem.
+ */
+class NetworkBootstrapper {
+ companion object {
+ // TODO This will probably need to change once we start using a bundled JVM
+ private val nodeInfoGenCmd = listOf(
+ "java",
+ "-jar",
+ "corda.jar",
+ "--just-generate-node-info"
+ )
+
+ private const val LOGS_DIR_NAME = "logs"
+
+ @JvmStatic
+ fun main(args: Array) {
+ val arg = args.singleOrNull() ?: throw IllegalArgumentException("Expecting single argument which is the nodes' parent directory")
+ NetworkBootstrapper().bootstrap(Paths.get(arg).toAbsolutePath().normalize())
+ }
+ }
+
+ fun bootstrap(directory: Path) {
+ directory.createDirectories()
+ println("Bootstrapping local network in $directory")
+ val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
+ require(nodeDirs.isNotEmpty()) { "No nodes found" }
+ println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
+ val processes = startNodeInfoGeneration(nodeDirs)
+ initialiseSerialization()
+ try {
+ println("Waiting for all nodes to generate their node-info files")
+ val nodeInfoFiles = gatherNodeInfoFiles(processes, nodeDirs)
+ println("Distributing all node info-files to all nodes")
+ distributeNodeInfos(nodeDirs, nodeInfoFiles)
+ println("Gathering notary identities")
+ val notaryInfos = gatherNotaryInfos(nodeInfoFiles)
+ println("Notary identities to be used in network-parameters file: ${notaryInfos.joinToString("; ") { it.prettyPrint() }}")
+ installNetworkParameters(notaryInfos, nodeDirs)
+ println("Bootstrapping complete!")
+ } finally {
+ _contextSerializationEnv.set(null)
+ processes.forEach { if (it.isAlive) it.destroyForcibly() }
+ }
+ }
+
+ private fun startNodeInfoGeneration(nodeDirs: List): List {
+ return nodeDirs.map { nodeDir ->
+ val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
+ ProcessBuilder(nodeInfoGenCmd)
+ .directory(nodeDir.toFile())
+ .redirectErrorStream(true)
+ .redirectOutput((logsDir / "node-info-gen.log").toFile())
+ .apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
+ .start()
+ }
+ }
+
+ private fun gatherNodeInfoFiles(processes: List, nodeDirs: List): List {
+ val timeOutInSeconds = 60L
+ return processes.zip(nodeDirs).map { (process, nodeDir) ->
+ check(process.waitFor(timeOutInSeconds, SECONDS)) {
+ "Node in ${nodeDir.fileName} took longer than ${timeOutInSeconds}s to generate its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
+ }
+ check(process.exitValue() == 0) {
+ "Node in ${nodeDir.fileName} exited with ${process.exitValue()} when generating its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
+ }
+ nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
+ }
+ }
+
+ private fun distributeNodeInfos(nodeDirs: List, nodeInfoFiles: List) {
+ for (nodeDir in nodeDirs) {
+ val additionalNodeInfosDir = (nodeDir / CordformNode.NODE_INFO_DIRECTORY).createDirectories()
+ for (nodeInfoFile in nodeInfoFiles) {
+ nodeInfoFile.copyToDirectory(additionalNodeInfosDir, StandardCopyOption.REPLACE_EXISTING)
+ }
+ }
+ }
+
+ private fun gatherNotaryInfos(nodeInfoFiles: List): List {
+ return nodeInfoFiles.mapNotNull { nodeInfoFile ->
+ // The config contains the notary type
+ val nodeConfig = ConfigFactory.parseFile((nodeInfoFile.parent / "node.conf").toFile())
+ if (nodeConfig.hasPath("notary")) {
+ val validating = nodeConfig.getConfig("notary").getBoolean("validating")
+ // And the node-info file contains the notary's identity
+ val nodeInfo = nodeInfoFile.readAll().deserialize().verified()
+ NotaryInfo(nodeInfo.notaryIdentity(), validating)
+ } else {
+ null
+ }
+ }.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
+ }
+
+ private fun installNetworkParameters(notaryInfos: List, nodeDirs: List) {
+ // TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
+ val copier = NetworkParametersCopier(NetworkParameters(
+ minimumPlatformVersion = 1,
+ notaries = notaryInfos,
+ modifiedTime = Instant.now(),
+ maxMessageSize = 10485760,
+ maxTransactionSize = 40000,
+ epoch = 1
+ ), overwriteFile = true)
+
+ nodeDirs.forEach(copier::install)
+ }
+
+ private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)"
+
+ private fun NodeInfo.notaryIdentity(): Party {
+ return when (legalIdentities.size) {
+ // Single node notaries have just one identity like all other nodes. This identity is the notary identity
+ 1 -> legalIdentities[0]
+ // Nodes which are part of a distributed notary have a second identity which is the composite identity of the
+ // cluster and is shared by all the other members. This is the notary identity.
+ 2 -> legalIdentities[1]
+ else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
+ }
+ }
+
+ // We need to to set serialization env, because generation of parameters is run from Cordform.
+ // KryoServerSerializationScheme is not accessible from nodeapi.
+ private fun initialiseSerialization() {
+ _contextSerializationEnv.set(SerializationEnvironmentImpl(
+ SerializationFactoryImpl().apply {
+ registerScheme(KryoParametersSerializationScheme)
+ registerScheme(AMQPServerSerializationScheme())
+ },
+ AMQP_P2P_CONTEXT)
+ )
+ }
+
+ private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
+ override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
+ return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
+ }
+ override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
+ override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
+ }
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt
similarity index 65%
rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt
rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt
index 871b1e514f..f784a4297b 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt
@@ -1,19 +1,21 @@
-package net.corda.nodeapi.internal
+package net.corda.nodeapi.internal.network
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.verify
import net.corda.core.identity.Party
+import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
+import net.corda.nodeapi.internal.crypto.X509Utilities
import java.security.SignatureException
import java.security.cert.CertPathValidatorException
import java.security.cert.X509Certificate
-import java.time.Duration
import java.time.Instant
-// TODO: Need more discussion on rather we should move this class out of internal.
+const val NETWORK_PARAMS_FILE_NAME = "network-parameters"
+
/**
* Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes.
*/
@@ -21,21 +23,21 @@ import java.time.Instant
data class NetworkMap(val nodeInfoHashes: List, val networkParameterHash: SecureHash)
/**
- * @property minimumPlatformVersion
- * @property notaries
- * @property eventHorizon
+ * @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network.
+ * @property notaries List of well known and trusted notary identities with information on validation type.
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
* @property maxTransactionSize Maximum permitted transaction size in bytes.
* @property modifiedTime
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
* of parameters.
*/
-// TODO Wire up the parameters
+// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network.
+// It needs separate design.
+// TODO Currently maxTransactionSize is not wired.
@CordaSerializable
data class NetworkParameters(
val minimumPlatformVersion: Int,
val notaries: List,
- val eventHorizon: Duration,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val modifiedTime: Instant,
@@ -45,6 +47,8 @@ data class NetworkParameters(
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
require(epoch > 0) { "epoch must be at least 1" }
+ require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
+ require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
}
}
@@ -56,20 +60,23 @@ data class NotaryInfo(val identity: Party, val validating: Boolean)
* contained within.
*/
@CordaSerializable
-class SignedNetworkMap(val raw: SerializedBytes, val sig: DigitalSignatureWithCert) {
+class SignedNetworkMap(val raw: SerializedBytes, val signature: DigitalSignatureWithCert) {
/**
* Return the deserialized NetworkMap if the signature and certificate can be verified.
*
* @throws CertPathValidatorException if the certificate path is invalid.
* @throws SignatureException if the signature is invalid.
*/
- @Throws(SignatureException::class)
- fun verified(): NetworkMap {
- sig.by.publicKey.verify(raw.bytes, sig)
+ @Throws(SignatureException::class, CertPathValidatorException::class)
+ fun verified(trustedRoot: X509Certificate): NetworkMap {
+ signature.by.publicKey.verify(raw.bytes, signature)
+ // Assume network map cert is under the default trust root.
+ X509Utilities.validateCertificateChain(trustedRoot, signature.by, trustedRoot)
return raw.deserialize()
}
}
// TODO: This class should reside in the [DigitalSignature] class.
+// TODO: Removing the val from signatureBytes causes serialisation issues
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
-class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
\ No newline at end of file
+class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt
new file mode 100644
index 0000000000..fe9b88a24e
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt
@@ -0,0 +1,35 @@
+package net.corda.nodeapi.internal.network
+
+import net.corda.core.crypto.Crypto
+import net.corda.core.crypto.SignedData
+import net.corda.core.crypto.sign
+import net.corda.core.internal.copyTo
+import net.corda.core.internal.div
+import net.corda.core.serialization.serialize
+import net.corda.nodeapi.internal.crypto.X509Utilities
+import java.nio.file.FileAlreadyExistsException
+import java.nio.file.Path
+import java.nio.file.StandardCopyOption
+import java.security.KeyPair
+
+class NetworkParametersCopier(
+ networkParameters: NetworkParameters,
+ signingKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
+ overwriteFile: Boolean = false
+) {
+ private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
+ private val serializedNetworkParameters = networkParameters.let {
+ val serialize = it.serialize()
+ val signature = signingKeyPair.sign(serialize)
+ SignedData(serialize, signature).serialize()
+ }
+
+ fun install(nodeDir: Path) {
+ try {
+ serializedNetworkParameters.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
+ } catch (e: FileAlreadyExistsException) {
+ // This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
+ // ignore this exception as we're happy with the existing file.
+ }
+ }
+}
\ No newline at end of file
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopier.kt
similarity index 98%
rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoFilesCopier.kt
rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopier.kt
index 00786c8da1..f11fa2265e 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoFilesCopier.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopier.kt
@@ -1,4 +1,4 @@
-package net.corda.nodeapi.internal
+package net.corda.nodeapi.internal.network
import net.corda.cordform.CordformNode
import net.corda.core.internal.ThreadBox
@@ -65,7 +65,6 @@ class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseabl
}
/**
- * @param nodeConfig the configuration to be removed.
* Remove the configuration of a node which is about to be stopped or already stopped.
* No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this
* one.
diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/Eventually.kt b/node-api/src/test/kotlin/net/corda/nodeapi/Eventually.kt
similarity index 96%
rename from testing/test-utils/src/main/kotlin/net/corda/testing/Eventually.kt
rename to node-api/src/test/kotlin/net/corda/nodeapi/Eventually.kt
index 693fffeca9..4c8136d1d3 100644
--- a/testing/test-utils/src/main/kotlin/net/corda/testing/Eventually.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/Eventually.kt
@@ -1,4 +1,4 @@
-package net.corda.testing
+package net.corda.nodeapi
import java.time.Duration
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 f3a24002cd..d0dd03ad6d 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
@@ -14,6 +14,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.*
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.services.MockAttachmentStorage
import org.junit.Assert.*
import org.junit.Rule
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 1d624a32f3..bbf8e5ca21 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
@@ -19,6 +19,8 @@ import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName
import net.corda.nodeapi.internal.serialization.withTokenContext
import net.corda.testing.*
+import net.corda.testing.internal.kryoSpecific
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.services.MockAttachmentStorage
import org.apache.commons.io.IOUtils
import org.junit.Assert.*
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt
new file mode 100644
index 0000000000..0ad99ae579
--- /dev/null
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt
@@ -0,0 +1,80 @@
+package net.corda.nodeapi.internal
+
+import net.corda.core.crypto.Crypto
+import net.corda.testing.ALICE_NAME
+import net.corda.testing.BOB_NAME
+import net.corda.testing.SerializationEnvironmentRule
+import net.corda.testing.internal.TestNodeInfoBuilder
+import net.corda.testing.internal.signWith
+import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.Rule
+import org.junit.Test
+import java.security.SignatureException
+
+class SignedNodeInfoTest {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
+
+ private val nodeInfoBuilder = TestNodeInfoBuilder()
+
+ @Test
+ fun `verifying single identity`() {
+ nodeInfoBuilder.addIdentity(ALICE_NAME)
+ val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
+ assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
+ }
+
+ @Test
+ fun `verifying multiple identities`() {
+ nodeInfoBuilder.addIdentity(ALICE_NAME)
+ nodeInfoBuilder.addIdentity(BOB_NAME)
+ val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
+ assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
+ }
+
+ @Test
+ fun `verifying missing signature`() {
+ val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
+ nodeInfoBuilder.addIdentity(BOB_NAME)
+ val nodeInfo = nodeInfoBuilder.build()
+ val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey))
+ assertThatThrownBy { signedNodeInfo.verified() }
+ .isInstanceOf(SignatureException::class.java)
+ .hasMessageContaining("Missing signatures")
+ }
+
+ @Test
+ fun `verifying extra signature`() {
+ val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
+ val nodeInfo = nodeInfoBuilder.build()
+ val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey, generateKeyPair().private))
+ assertThatThrownBy { signedNodeInfo.verified() }
+ .isInstanceOf(SignatureException::class.java)
+ .hasMessageContaining("Extra signatures")
+ }
+
+ @Test
+ fun `verifying incorrect signature`() {
+ nodeInfoBuilder.addIdentity(ALICE_NAME)
+ val nodeInfo = nodeInfoBuilder.build()
+ val signedNodeInfo = nodeInfo.signWith(listOf(generateKeyPair().private))
+ assertThatThrownBy { signedNodeInfo.verified() }
+ .isInstanceOf(SignatureException::class.java)
+ .hasMessageContaining(ALICE_NAME.toString())
+ }
+
+ @Test
+ fun `verifying with signatures in wrong order`() {
+ val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
+ val (_, bobKey) = nodeInfoBuilder.addIdentity(BOB_NAME)
+ val nodeInfo = nodeInfoBuilder.build()
+ val signedNodeInfo = nodeInfo.signWith(listOf(bobKey, aliceKey))
+ assertThatThrownBy { signedNodeInfo.verified() }
+ .isInstanceOf(SignatureException::class.java)
+ .hasMessageContaining(ALICE_NAME.toString())
+ }
+
+ private fun generateKeyPair() = Crypto.generateKeyPair()
+}
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt
index 30c8004659..8435ff5791 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt
@@ -51,7 +51,7 @@ class X509UtilitiesTest {
val bob = TestIdentity(BOB_NAME, 80)
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
val BOB get() = bob.party
- val BOB_PUBKEY get() = bob.pubkey
+ val BOB_PUBKEY get() = bob.publicKey
}
@Rule
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopierTest.kt
similarity index 94%
rename from node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt
rename to node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopierTest.kt
index c14f2c854d..d4d95ba6fd 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NodeInfoFilesCopierTest.kt
@@ -1,8 +1,7 @@
-package net.corda.nodeapi
+package net.corda.nodeapi.internal.network
import net.corda.cordform.CordformNode
-import net.corda.nodeapi.internal.NodeInfoFilesCopier
-import net.corda.testing.eventually
+import net.corda.nodeapi.eventually
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -15,14 +14,7 @@ import java.util.concurrent.TimeUnit
import kotlin.streams.toList
import kotlin.test.assertEquals
-/**
- * tests for [NodeInfoFilesCopier]
- */
class NodeInfoFilesCopierTest {
-
- @Rule @JvmField var folder = TemporaryFolder()
- private val rootPath get() = folder.root.toPath()
- private val scheduler = TestScheduler()
companion object {
private const val ORGANIZATION = "Organization"
private const val NODE_1_PATH = "node1"
@@ -34,6 +26,13 @@ class NodeInfoFilesCopierTest {
private val BAD_NODE_INFO_NAME = "something"
}
+ @Rule
+ @JvmField
+ val folder = TemporaryFolder()
+
+ private val rootPath get() = folder.root.toPath()
+ private val scheduler = TestScheduler()
+
private fun nodeDir(nodeBaseDir : String) = rootPath.resolve(nodeBaseDir).resolve(ORGANIZATION.toLowerCase())
private val node1RootPath by lazy { nodeDir(NODE_1_PATH) }
@@ -41,7 +40,7 @@ class NodeInfoFilesCopierTest {
private val node1AdditionalNodeInfoPath by lazy { node1RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
private val node2AdditionalNodeInfoPath by lazy { node2RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
- lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier
+ private lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier
@Before
fun setUp() {
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt
index 80ee09701a..e24dc671dd 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt
@@ -5,6 +5,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.*
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
@@ -23,7 +24,7 @@ class ContractAttachmentSerializerTest {
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
private lateinit var contextWithToken: SerializationContext
- private val mockServices = MockServices(rigorousMock(), CordaX500Name("MegaCorp", "London", "GB"))
+ private val mockServices = MockServices(emptyList(), rigorousMock(), CordaX500Name("MegaCorp", "London", "GB"))
@Before
fun setup() {
factory = testSerialization.env.serializationFactory
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt
index 654bf323e3..1743d9ee46 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt
@@ -13,7 +13,7 @@ import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
import net.corda.nodeapi.internal.serialization.kryo.CordaKryo
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.services.MockAttachmentStorage
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
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 508a40f7c6..d48558a6fe 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
@@ -33,7 +33,7 @@ import kotlin.test.assertTrue
class KryoTests {
companion object {
- private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).pubkey
+ private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
}
@Rule
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 d6796023b3..2e7e4e24ce 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
@@ -8,8 +8,8 @@ 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.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
-import net.corda.testing.amqpSpecific
-import net.corda.testing.kryoSpecific
+import net.corda.testing.internal.amqpSpecific
+import net.corda.testing.internal.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions
import org.junit.Assert.assertArrayEquals
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 3bb028f437..ef89c36f62 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
@@ -9,8 +9,8 @@ import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.SerializationEnvironmentRule
-import net.corda.testing.amqpSpecific
-import net.corda.testing.kryoSpecific
+import net.corda.testing.internal.amqpSpecific
+import net.corda.testing.internal.kryoSpecific
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertArrayEquals
import org.junit.Rule
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 a51f5934e1..70f165d9a4 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
@@ -8,7 +8,7 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.serialization.kryo.CordaKryo
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
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 03c4fb08c1..4d66588057 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
@@ -6,8 +6,8 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
+import net.corda.testing.internal.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
-import net.corda.testing.kryoSpecific
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Rule
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 68bc1a5786..9871bff1c6 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
@@ -29,6 +29,7 @@ import org.apache.qpid.proton.codec.EncoderImpl
import org.assertj.core.api.Assertions.*
import org.junit.Assert.*
import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.IOException
@@ -49,11 +50,15 @@ class SerializationOutputTests {
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val MEGA_CORP get() = megaCorp.party
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
- val MINI_CORP_PUBKEY get() = miniCorp.pubkey
+ val MINI_CORP_PUBKEY get() = miniCorp.publicKey
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
+
data class Foo(val bar: String, val pub: Int)
data class testFloat(val f: Float)
@@ -473,9 +478,9 @@ class SerializationOutputTests {
assertSerializedThrowableEquivalent(t, desThrowable)
}
- private fun serdesThrowableWithInternalInfo(t: Throwable, factory: SerializerFactory, factory2: SerializerFactory, expectedEqual: Boolean = true): Throwable = withTestSerialization {
+ private fun serdesThrowableWithInternalInfo(t: Throwable, factory: SerializerFactory, factory2: SerializerFactory, expectedEqual: Boolean = true): Throwable {
val newContext = SerializationFactory.defaultFactory.defaultContext.withProperty(CommonPropertyNames.IncludeInternalInfo, true)
- SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
+ return SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
}
@Test
diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt
new file mode 100644
index 0000000000..4c2f0ee881
--- /dev/null
+++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt
@@ -0,0 +1,242 @@
+package net.corda.node.amqp
+
+import com.nhaarman.mockito_kotlin.any
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.whenever
+import net.corda.core.internal.div
+import net.corda.core.node.NodeInfo
+import net.corda.core.node.services.NetworkMapCache
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.toBase58String
+import net.corda.node.internal.protonwrapper.netty.AMQPServer
+import net.corda.node.internal.security.RPCSecurityManager
+import net.corda.node.services.config.*
+import net.corda.node.services.messaging.ArtemisMessagingClient
+import net.corda.node.services.messaging.ArtemisMessagingServer
+import net.corda.nodeapi.internal.ArtemisMessagingComponent
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
+import net.corda.nodeapi.internal.crypto.loadKeyStore
+import net.corda.testing.*
+import net.corda.testing.internal.rigorousMock
+import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
+import org.apache.activemq.artemis.api.core.RoutingType
+import org.apache.activemq.artemis.api.core.SimpleString
+import org.junit.Assert.assertArrayEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import rx.Observable
+import java.util.*
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+class AMQPBridgeTest {
+ @Rule
+ @JvmField
+ val temporaryFolder = TemporaryFolder()
+
+ private val ALICE = TestIdentity(ALICE_NAME)
+ private val BOB = TestIdentity(BOB_NAME)
+
+ private val artemisPort = freePort()
+ private val artemisPort2 = freePort()
+ private val amqpPort = freePort()
+ private val artemisAddress = NetworkHostAndPort("localhost", artemisPort)
+ private val artemisAddress2 = NetworkHostAndPort("localhost", artemisPort2)
+ private val amqpAddress = NetworkHostAndPort("localhost", amqpPort)
+
+ private abstract class AbstractNodeConfiguration : NodeConfiguration
+
+ @Test
+ fun `test acked and nacked messages`() {
+ // Create local queue
+ val sourceQueueName = "internal.peers." + BOB.publicKey.toBase58String()
+ val (artemisServer, artemisClient) = createArtemis(sourceQueueName)
+
+ // Pre-populate local queue with 3 messages
+ val artemis = artemisClient.started!!
+ for (i in 0 until 3) {
+ val artemisMessage = artemis.session.createMessage(true).apply {
+ putIntProperty("CountProp", i)
+ writeBodyBufferBytes("Test$i".toByteArray())
+ // Use the magic deduplication property built into Artemis as our message identity too
+ putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
+ }
+ artemis.producer.send(sourceQueueName, artemisMessage)
+ }
+
+ //Create target server
+ val amqpServer = createAMQPServer()
+
+ val receive = amqpServer.onReceive.toBlocking().iterator
+ amqpServer.start()
+
+ val received1 = receive.next()
+ val messageID1 = received1.applicationProperties["CountProp"] as Int
+ assertArrayEquals("Test$messageID1".toByteArray(), received1.payload)
+ assertEquals(0, messageID1)
+ received1.complete(true) // Accept first message
+
+ val received2 = receive.next()
+ val messageID2 = received2.applicationProperties["CountProp"] as Int
+ assertArrayEquals("Test$messageID2".toByteArray(), received2.payload)
+ assertEquals(1, messageID2)
+ received2.complete(false) // Reject message
+
+ while (true) {
+ val received3 = receive.next()
+ val messageID3 = received3.applicationProperties["CountProp"] as Int
+ assertArrayEquals("Test$messageID3".toByteArray(), received3.payload)
+ assertNotEquals(0, messageID3)
+ if (messageID3 != 1) { // keep rejecting any batched items following rejection
+ received3.complete(false)
+ } else { // beginnings of replay so accept again
+ received3.complete(true)
+ break
+ }
+ }
+
+ while (true) {
+ val received4 = receive.next()
+ val messageID4 = received4.applicationProperties["CountProp"] as Int
+ assertArrayEquals("Test$messageID4".toByteArray(), received4.payload)
+ if (messageID4 != 1) { // we may get a duplicate of the rejected message, in which case skip
+ assertEquals(2, messageID4) // next message should be in order though
+ break
+ }
+ received4.complete(true)
+ }
+
+ // Send a fresh item and check receive
+ val artemisMessage = artemis.session.createMessage(true).apply {
+ putIntProperty("CountProp", -1)
+ writeBodyBufferBytes("Test_end".toByteArray())
+ // Use the magic deduplication property built into Artemis as our message identity too
+ putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
+ }
+ artemis.producer.send(sourceQueueName, artemisMessage)
+
+ val received5 = receive.next()
+ val messageID5 = received5.applicationProperties["CountProp"] as Int
+ assertArrayEquals("Test_end".toByteArray(), received5.payload)
+ assertEquals(-1, messageID5) // next message should be in order
+ received5.complete(true)
+
+ amqpServer.stop()
+ artemisClient.stop()
+ artemisServer.stop()
+ }
+
+ @Test
+ fun `Test legacy bridge still works`() {
+ // Create local queue
+ val sourceQueueName = "internal.peers." + ALICE.publicKey.toBase58String()
+ val (artemisLegacyServer, artemisLegacyClient) = createLegacyArtemis(sourceQueueName)
+
+
+ val (artemisServer, artemisClient) = createArtemis(null)
+
+ val artemis = artemisLegacyClient.started!!
+ for (i in 0 until 3) {
+ val artemisMessage = artemis.session.createMessage(true).apply {
+ putIntProperty("CountProp", i)
+ writeBodyBufferBytes("Test$i".toByteArray())
+ // Use the magic deduplication property built into Artemis as our message identity too
+ putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
+ }
+ artemis.producer.send(sourceQueueName, artemisMessage)
+ }
+
+
+ val subs = artemisClient.started!!.session.createConsumer(P2P_QUEUE)
+ for (i in 0 until 3) {
+ val msg = subs.receive()
+ val messageBody = ByteArray(msg.bodySize).apply { msg.bodyBuffer.readBytes(this) }
+ assertArrayEquals("Test$i".toByteArray(), messageBody)
+ assertEquals(i, msg.getIntProperty("CountProp"))
+ }
+
+ artemisClient.stop()
+ artemisServer.stop()
+ artemisLegacyClient.stop()
+ artemisLegacyServer.stop()
+
+ }
+
+ private fun createArtemis(sourceQueueName: String?): Pair {
+ val artemisConfig = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
+ doReturn(ALICE_NAME).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ doReturn("").whenever(it).exportJMXto
+ doReturn(emptyList()).whenever(it).certificateChainCheckPolicies
+ doReturn(true).whenever(it).useAMQPBridges
+ }
+ artemisConfig.configureWithDevSSLCertificate()
+ val networkMap = rigorousMock().also {
+ doReturn(Observable.never()).whenever(it).changed
+ doReturn(listOf(NodeInfo(listOf(amqpAddress), listOf(BOB.identity), 1, 1L))).whenever(it).getNodesByLegalIdentityKey(any())
+ }
+ val userService = rigorousMock()
+ val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
+ val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress, MAX_MESSAGE_SIZE)
+ artemisServer.start()
+ artemisClient.start()
+ val artemis = artemisClient.started!!
+ if (sourceQueueName != null) {
+ // Local queue for outgoing messages
+ artemis.session.createQueue(sourceQueueName, RoutingType.MULTICAST, sourceQueueName, true)
+ }
+ return Pair(artemisServer, artemisClient)
+ }
+
+ private fun createLegacyArtemis(sourceQueueName: String): Pair {
+ val artemisConfig = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath() / "artemis2").whenever(it).baseDirectory
+ doReturn(BOB_NAME).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ doReturn("").whenever(it).exportJMXto
+ doReturn(emptyList()).whenever(it).certificateChainCheckPolicies
+ doReturn(false).whenever(it).useAMQPBridges
+ doReturn(ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0))).whenever(it).activeMQServer
+ }
+ artemisConfig.configureWithDevSSLCertificate()
+ val networkMap = rigorousMock().also {
+ doReturn(Observable.never()).whenever(it).changed
+ doReturn(listOf(NodeInfo(listOf(artemisAddress), listOf(ALICE.identity), 1, 1L))).whenever(it).getNodesByLegalIdentityKey(any())
+ }
+ val userService = rigorousMock()
+ val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort2, null, networkMap, userService, MAX_MESSAGE_SIZE)
+ val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress2, MAX_MESSAGE_SIZE)
+ artemisServer.start()
+ artemisClient.start()
+ val artemis = artemisClient.started!!
+ // Local queue for outgoing messages
+ artemis.session.createQueue(sourceQueueName, RoutingType.MULTICAST, sourceQueueName, true)
+ return Pair(artemisServer, artemisClient)
+ }
+
+ private fun createAMQPServer(): AMQPServer {
+ val serverConfig = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
+ doReturn(BOB_NAME).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ }
+ serverConfig.configureWithDevSSLCertificate()
+
+ val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
+ val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
+ val amqpServer = AMQPServer("0.0.0.0",
+ amqpPort,
+ ArtemisMessagingComponent.PEER_USER,
+ ArtemisMessagingComponent.PEER_USER,
+ serverKeystore,
+ serverConfig.keyStorePassword,
+ serverTruststore,
+ trace = true)
+ return amqpServer
+ }
+}
\ No newline at end of file
diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt
new file mode 100644
index 0000000000..871c7de400
--- /dev/null
+++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt
@@ -0,0 +1,309 @@
+package net.corda.node.amqp
+
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.whenever
+import io.netty.channel.EventLoopGroup
+import io.netty.channel.nio.NioEventLoopGroup
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.internal.div
+import net.corda.core.node.services.NetworkMapCache
+import net.corda.core.toFuture
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.node.internal.protonwrapper.messages.MessageStatus
+import net.corda.node.internal.protonwrapper.netty.AMQPClient
+import net.corda.node.internal.protonwrapper.netty.AMQPServer
+import net.corda.node.internal.security.RPCSecurityManager
+import net.corda.node.services.config.CertChainPolicyConfig
+import net.corda.node.services.config.NodeConfiguration
+import net.corda.node.services.config.configureWithDevSSLCertificate
+import net.corda.node.services.messaging.ArtemisMessagingClient
+import net.corda.node.services.messaging.ArtemisMessagingServer
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
+import net.corda.nodeapi.internal.crypto.loadKeyStore
+import net.corda.testing.*
+import net.corda.testing.internal.rigorousMock
+import org.apache.activemq.artemis.api.core.RoutingType
+import org.junit.Assert.assertArrayEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import rx.Observable.never
+import kotlin.test.assertEquals
+
+class ProtonWrapperTests {
+ @Rule
+ @JvmField
+ val temporaryFolder = TemporaryFolder()
+
+ private val serverPort = freePort()
+ private val serverPort2 = freePort()
+ private val artemisPort = freePort()
+
+ private abstract class AbstractNodeConfiguration : NodeConfiguration
+
+ @Test
+ fun `Simple AMPQ Client to Server`() {
+ val amqpServer = createServer(serverPort)
+ amqpServer.use {
+ amqpServer.start()
+ val receiveSubs = amqpServer.onReceive.subscribe {
+ assertEquals(BOB_NAME.toString(), it.sourceLegalName)
+ assertEquals("p2p.inbound", it.topic)
+ assertEquals("Test", String(it.payload))
+ it.complete(true)
+ }
+ val amqpClient = createClient()
+ amqpClient.use {
+ val serverConnected = amqpServer.onConnection.toFuture()
+ val clientConnected = amqpClient.onConnection.toFuture()
+ amqpClient.start()
+ val serverConnect = serverConnected.get()
+ assertEquals(true, serverConnect.connected)
+ assertEquals(BOB_NAME, CordaX500Name.parse(serverConnect.remoteCert!!.subject.toString()))
+ val clientConnect = clientConnected.get()
+ assertEquals(true, clientConnect.connected)
+ assertEquals(ALICE_NAME, CordaX500Name.parse(clientConnect.remoteCert!!.subject.toString()))
+ val msg = amqpClient.createMessage("Test".toByteArray(),
+ "p2p.inbound",
+ ALICE_NAME.toString(),
+ emptyMap())
+ amqpClient.write(msg)
+ assertEquals(MessageStatus.Acknowledged, msg.onComplete.get())
+ receiveSubs.unsubscribe()
+ }
+ }
+ }
+
+ @Test
+ fun `AMPQ Client refuses to connect to unexpected server`() {
+ val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB"))
+ amqpServer.use {
+ amqpServer.start()
+ val amqpClient = createClient()
+ amqpClient.use {
+ val clientConnected = amqpClient.onConnection.toFuture()
+ amqpClient.start()
+ val clientConnect = clientConnected.get()
+ assertEquals(false, clientConnect.connected)
+ }
+ }
+ }
+
+ @Test
+ fun `Client Failover for multiple IP`() {
+ val amqpServer = createServer(serverPort)
+ val amqpServer2 = createServer(serverPort2)
+ val amqpClient = createClient()
+ try {
+ val serverConnected = amqpServer.onConnection.toFuture()
+ val serverConnected2 = amqpServer2.onConnection.toFuture()
+ val clientConnected = amqpClient.onConnection.toBlocking().iterator
+ amqpServer.start()
+ amqpClient.start()
+ val serverConn1 = serverConnected.get()
+ assertEquals(true, serverConn1.connected)
+ assertEquals(BOB_NAME, CordaX500Name.parse(serverConn1.remoteCert!!.subject.toString()))
+ val connState1 = clientConnected.next()
+ assertEquals(true, connState1.connected)
+ assertEquals(ALICE_NAME, CordaX500Name.parse(connState1.remoteCert!!.subject.toString()))
+ assertEquals(serverPort, connState1.remoteAddress.port)
+
+ // Fail over
+ amqpServer2.start()
+ amqpServer.stop()
+ val connState2 = clientConnected.next()
+ assertEquals(false, connState2.connected)
+ assertEquals(serverPort, connState2.remoteAddress.port)
+ val serverConn2 = serverConnected2.get()
+ assertEquals(true, serverConn2.connected)
+ assertEquals(BOB_NAME, CordaX500Name.parse(serverConn2.remoteCert!!.subject.toString()))
+ val connState3 = clientConnected.next()
+ assertEquals(true, connState3.connected)
+ assertEquals(ALICE_NAME, CordaX500Name.parse(connState3.remoteCert!!.subject.toString()))
+ assertEquals(serverPort2, connState3.remoteAddress.port)
+
+ // Fail back
+ amqpServer.start()
+ amqpServer2.stop()
+ val connState4 = clientConnected.next()
+ assertEquals(false, connState4.connected)
+ assertEquals(serverPort2, connState4.remoteAddress.port)
+ val serverConn3 = serverConnected.get()
+ assertEquals(true, serverConn3.connected)
+ assertEquals(BOB_NAME, CordaX500Name.parse(serverConn3.remoteCert!!.subject.toString()))
+ val connState5 = clientConnected.next()
+ assertEquals(true, connState5.connected)
+ assertEquals(ALICE_NAME, CordaX500Name.parse(connState5.remoteCert!!.subject.toString()))
+ assertEquals(serverPort, connState5.remoteAddress.port)
+ } finally {
+ amqpClient.close()
+ amqpServer.close()
+ amqpServer2.close()
+ }
+ }
+
+ @Test
+ fun `Send a message from AMQP to Artemis inbox`() {
+ val (server, artemisClient) = createArtemisServerAndClient()
+ val amqpClient = createClient()
+ val clientConnected = amqpClient.onConnection.toFuture()
+ amqpClient.start()
+ assertEquals(true, clientConnected.get().connected)
+ assertEquals(CHARLIE_NAME, CordaX500Name.parse(clientConnected.get().remoteCert!!.subject.toString()))
+ val artemis = artemisClient.started!!
+ val sendAddress = "p2p.inbound"
+ artemis.session.createQueue(sendAddress, RoutingType.MULTICAST, "queue", true)
+ val consumer = artemis.session.createConsumer("queue")
+ val testData = "Test".toByteArray()
+ val testProperty = mutableMapOf()
+ testProperty["TestProp"] = "1"
+ val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
+ amqpClient.write(message)
+ assertEquals(MessageStatus.Acknowledged, message.onComplete.get())
+ val received = consumer.receive()
+ assertEquals("1", received.getStringProperty("TestProp"))
+ assertArrayEquals(testData, ByteArray(received.bodySize).apply { received.bodyBuffer.readBytes(this) })
+ amqpClient.stop()
+ artemisClient.stop()
+ server.stop()
+ }
+
+ @Test
+ fun `shared AMQPClient threadpool tests`() {
+ val amqpServer = createServer(serverPort)
+ amqpServer.use {
+ val connectionEvents = amqpServer.onConnection.toBlocking().iterator
+ amqpServer.start()
+ val sharedThreads = NioEventLoopGroup()
+ val amqpClient1 = createSharedThreadsClient(sharedThreads, 0)
+ val amqpClient2 = createSharedThreadsClient(sharedThreads, 1)
+ amqpClient1.start()
+ val connection1 = connectionEvents.next()
+ assertEquals(true, connection1.connected)
+ val connection1ID = CordaX500Name.parse(connection1.remoteCert!!.subject.toString())
+ assertEquals("client 0", connection1ID.organisationUnit)
+ val source1 = connection1.remoteAddress
+ amqpClient2.start()
+ val connection2 = connectionEvents.next()
+ assertEquals(true, connection2.connected)
+ val connection2ID = CordaX500Name.parse(connection2.remoteCert!!.subject.toString())
+ assertEquals("client 1", connection2ID.organisationUnit)
+ val source2 = connection2.remoteAddress
+ // Stopping one shouldn't disconnect the other
+ amqpClient1.stop()
+ val connection3 = connectionEvents.next()
+ assertEquals(false, connection3.connected)
+ assertEquals(source1, connection3.remoteAddress)
+ assertEquals(false, amqpClient1.connected)
+ assertEquals(true, amqpClient2.connected)
+ // Now shutdown both
+ amqpClient2.stop()
+ val connection4 = connectionEvents.next()
+ assertEquals(false, connection4.connected)
+ assertEquals(source2, connection4.remoteAddress)
+ assertEquals(false, amqpClient1.connected)
+ assertEquals(false, amqpClient2.connected)
+ // Now restarting one should work
+ amqpClient1.start()
+ val connection5 = connectionEvents.next()
+ assertEquals(true, connection5.connected)
+ val connection5ID = CordaX500Name.parse(connection5.remoteCert!!.subject.toString())
+ assertEquals("client 0", connection5ID.organisationUnit)
+ assertEquals(true, amqpClient1.connected)
+ assertEquals(false, amqpClient2.connected)
+ // Cleanup
+ amqpClient1.stop()
+ sharedThreads.shutdownGracefully()
+ sharedThreads.terminationFuture().sync()
+ }
+ }
+
+ private fun createArtemisServerAndClient(): Pair {
+ val artemisConfig = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
+ doReturn(CHARLIE_NAME).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ doReturn("").whenever(it).exportJMXto
+ doReturn(emptyList()).whenever(it).certificateChainCheckPolicies
+ doReturn(true).whenever(it).useAMQPBridges
+ }
+ artemisConfig.configureWithDevSSLCertificate()
+
+ val networkMap = rigorousMock().also {
+ doReturn(never()).whenever(it).changed
+ }
+ val userService = rigorousMock()
+ val server = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
+ val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), MAX_MESSAGE_SIZE)
+ server.start()
+ client.start()
+ return Pair(server, client)
+ }
+
+ private fun createClient(): AMQPClient {
+ val clientConfig = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
+ doReturn(BOB_NAME).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ }
+ clientConfig.configureWithDevSSLCertificate()
+
+ val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
+ val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
+ val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", serverPort),
+ NetworkHostAndPort("localhost", serverPort2),
+ NetworkHostAndPort("localhost", artemisPort)),
+ setOf(ALICE_NAME, CHARLIE_NAME),
+ PEER_USER,
+ PEER_USER,
+ clientKeystore,
+ clientConfig.keyStorePassword,
+ clientTruststore, true)
+ return amqpClient
+ }
+
+ private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient {
+ val clientConfig = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath() / "client_%$id").whenever(it).baseDirectory
+ doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ }
+ clientConfig.configureWithDevSSLCertificate()
+
+ val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
+ val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
+ val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", serverPort)),
+ setOf(ALICE_NAME),
+ PEER_USER,
+ PEER_USER,
+ clientKeystore,
+ clientConfig.keyStorePassword,
+ clientTruststore, true, sharedEventGroup)
+ return amqpClient
+ }
+
+ private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer {
+ val serverConfig = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
+ doReturn(name).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ }
+ serverConfig.configureWithDevSSLCertificate()
+
+ val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
+ val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
+ val amqpServer = AMQPServer("0.0.0.0",
+ port,
+ PEER_USER,
+ PEER_USER,
+ serverKeystore,
+ serverConfig.keyStorePassword,
+ serverTruststore)
+ return amqpServer
+ }
+
+}
\ No newline at end of file
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 e035e2d854..3b5159e6cb 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
@@ -24,15 +24,21 @@ import net.corda.testing.*
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
+import net.corda.testing.internal.withoutTestSerialization
import net.corda.testing.services.MockAttachmentStorage
+import net.corda.testing.internal.rigorousMock
import org.junit.Assert.assertEquals
import org.junit.ClassRule
+import org.junit.Rule
import org.junit.Test
import java.net.URLClassLoader
import java.nio.file.Files
import kotlin.test.assertFailsWith
class AttachmentLoadingTests : IntegrationTest() {
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
private val attachments = MockAttachmentStorage()
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
private val cordapp get() = provider.cordapps.first()
@@ -83,7 +89,7 @@ class AttachmentLoadingTests : IntegrationTest() {
}
@Test
- fun `test a wire transaction has loaded the correct attachment`() = withTestSerialization {
+ fun `test a wire transaction has loaded the correct attachment`() {
val appClassLoader = appContext.classLoader
val contractClass = appClassLoader.loadClass(ISOLATED_CONTRACT_ID).asSubclass(Contract::class.java)
val generateInitialMethod = contractClass.getDeclaredMethod("generateInitial", PartyAndReference::class.java, Integer.TYPE, Party::class.java)
@@ -99,7 +105,7 @@ class AttachmentLoadingTests : IntegrationTest() {
}
@Test
- fun `test that attachments retrieved over the network are not used for code`() {
+ fun `test that attachments retrieved over the network are not used for code`() = withoutTestSerialization {
driver {
installIsolatedCordappTo(bankAName)
val (bankA, bankB) = createTwoNodes()
@@ -107,15 +113,17 @@ class AttachmentLoadingTests : IntegrationTest() {
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
}
}
+ Unit
}
@Test
- fun `tests that if the attachment is loaded on both sides already that a flow can run`() {
+ fun `tests that if the attachment is loaded on both sides already that a flow can run`() = withoutTestSerialization {
driver {
installIsolatedCordappTo(bankAName)
installIsolatedCordappTo(bankBName)
val (bankA, bankB) = createTwoNodes()
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
}
+ Unit
}
}
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt
index 624590fab8..4e6ba5d42f 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt
@@ -24,10 +24,12 @@ import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas
-import net.corda.nodeapi.internal.NetworkParametersCopier
-import net.corda.nodeapi.internal.NotaryInfo
import net.corda.nodeapi.internal.ServiceIdentityGenerator
-import net.corda.testing.*
+import net.corda.nodeapi.internal.network.NotaryInfo
+import net.corda.testing.chooseIdentity
+import net.corda.nodeapi.internal.network.NetworkParametersCopier
+import net.corda.testing.IntegrationTest
+import net.corda.testing.IntegrationTestSchemas
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockNetwork
@@ -55,7 +57,7 @@ class BFTNotaryServiceTests : IntegrationTest() {
@Before
fun before() {
- mockNet = MockNetwork()
+ mockNet = MockNetwork(emptyList())
node = mockNet.createNode()
}
@After
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt
index 8ca2736f7a..c890e1f6cd 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt
@@ -1,26 +1,37 @@
package net.corda.node.services.network
+import net.corda.core.crypto.SignedData
+import net.corda.core.internal.list
+import net.corda.core.internal.readAll
import net.corda.core.node.NodeInfo
+import net.corda.core.serialization.deserialize
+import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
+import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
+import net.corda.nodeapi.internal.network.NetworkParameters
+import net.corda.testing.ALICE_NAME
+import net.corda.testing.BOB_NAME
import net.corda.testing.*
import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.PortAllocation
import net.corda.testing.node.internal.internalDriver
-import net.corda.testing.node.network.NetworkMapServer
+import net.corda.testing.node.internal.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
+import org.junit.*
import java.net.URL
+import kotlin.test.assertEquals
class NetworkMapTest : IntegrationTest() {
companion object {
- @ClassRule @JvmField
+ @ClassRule
+ @JvmField
val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName(),
DUMMY_NOTARY_NAME.toDatabaseSchemaName())
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule(true)
private val cacheTimeout = 1.seconds
private val portAllocation = PortAllocation.Incremental(10000)
@@ -39,9 +50,22 @@ class NetworkMapTest : IntegrationTest() {
networkMapServer.close()
}
+ @Test
+ fun `node correctly downloads and saves network parameters file on startup`() {
+ internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ val networkParameters = alice.configuration.baseDirectory
+ .list { paths -> paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().get() }
+ .readAll()
+ .deserialize>()
+ .verified()
+ assertEquals(NetworkMapServer.stubNetworkParameter, networkParameters)
+ }
+ }
+
@Test
fun `nodes can see each other using the http network map`() {
- internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
+ internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) {
val alice = startNode(providedName = ALICE_NAME)
val bob = startNode(providedName = BOB_NAME)
val notaryNode = defaultNotaryNode.get()
@@ -56,7 +80,7 @@ class NetworkMapTest : IntegrationTest() {
@Test
fun `nodes process network map add updates correctly when adding new node to network map`() {
- internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
+ internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) {
val alice = startNode(providedName = ALICE_NAME)
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
@@ -77,7 +101,7 @@ class NetworkMapTest : IntegrationTest() {
@Test
fun `nodes process network map remove updates correctly`() {
- internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
+ internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) {
val alice = startNode(providedName = ALICE_NAME)
val bob = startNode(providedName = BOB_NAME)
val notaryNode = defaultNotaryNode.get()
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 8977afda78..b813558eca 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
@@ -3,14 +3,15 @@ package net.corda.node.services.network
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import net.corda.cordform.CordformNode
-import net.corda.core.crypto.SignedData
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.KeyManagementService
-import net.corda.core.serialization.serialize
-import net.corda.nodeapi.internal.NodeInfoFilesCopier
-import net.corda.testing.*
+import net.corda.nodeapi.internal.SignedNodeInfo
+import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
+import net.corda.testing.ALICE_NAME
+import net.corda.testing.SerializationEnvironmentRule
+import net.corda.testing.internal.createNodeInfoAndSigned
import net.corda.testing.node.MockKeyManagementService
import net.corda.testing.node.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat
@@ -27,20 +28,20 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
class NodeInfoWatcherTest {
- private companion object {
- val alice = TestIdentity(ALICE_NAME, 70)
- val nodeInfo = NodeInfo(listOf(), listOf(alice.identity), 0, 0)
- }
-
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
+
@Rule
@JvmField
val tempFolder = TemporaryFolder()
- private lateinit var nodeInfoPath: Path
+
private val scheduler = TestScheduler()
private val testSubscriber = TestSubscriber()
+
+ private lateinit var nodeInfo: NodeInfo
+ private lateinit var signedNodeInfo: SignedNodeInfo
+ private lateinit var nodeInfoPath: Path
private lateinit var keyManagementService: KeyManagementService
// Object under test
@@ -48,8 +49,11 @@ class NodeInfoWatcherTest {
@Before
fun start() {
+ val nodeInfoAndSigned = createNodeInfoAndSigned(ALICE_NAME)
+ nodeInfo = nodeInfoAndSigned.first
+ signedNodeInfo = nodeInfoAndSigned.second
val identityService = makeTestIdentityService()
- keyManagementService = MockKeyManagementService(identityService, alice.key)
+ keyManagementService = MockKeyManagementService(identityService)
nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler)
nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY
}
@@ -58,7 +62,6 @@ class NodeInfoWatcherTest {
fun `save a NodeInfo`() {
assertEquals(0,
tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size)
- val signedNodeInfo = SignedData(nodeInfo.serialize(), keyManagementService.sign(nodeInfo.serialize().bytes, nodeInfo.legalIdentities.first().owningKey))
NodeInfoWatcher.saveToFile(tempFolder.root.toPath(), signedNodeInfo)
val nodeInfoFiles = tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }
@@ -74,7 +77,6 @@ class NodeInfoWatcherTest {
fun `save a NodeInfo to JimFs`() {
val jimFs = Jimfs.newFileSystem(Configuration.unix())
val jimFolder = jimFs.getPath("/nodeInfo")
- val signedNodeInfo = SignedData(nodeInfo.serialize(), keyManagementService.sign(nodeInfo.serialize().bytes, nodeInfo.legalIdentities.first().owningKey))
NodeInfoWatcher.saveToFile(jimFolder, signedNodeInfo)
}
@@ -82,11 +84,9 @@ class NodeInfoWatcherTest {
fun `load an empty Directory`() {
nodeInfoPath.createDirectories()
- val subscription = nodeInfoWatcher.nodeInfoUpdates()
- .subscribe(testSubscriber)
+ val subscription = nodeInfoWatcher.nodeInfoUpdates().subscribe(testSubscriber)
try {
advanceTime()
-
val readNodes = testSubscriber.onNextEvents.distinct()
assertEquals(0, readNodes.size)
} finally {
@@ -96,15 +96,13 @@ class NodeInfoWatcherTest {
@Test
fun `load a non empty Directory`() {
- createNodeInfoFileInPath(nodeInfo)
+ createNodeInfoFileInPath()
- val subscription = nodeInfoWatcher.nodeInfoUpdates()
- .subscribe(testSubscriber)
+ val subscription = nodeInfoWatcher.nodeInfoUpdates().subscribe(testSubscriber)
advanceTime()
try {
val readNodes = testSubscriber.onNextEvents.distinct()
-
assertEquals(1, readNodes.size)
assertEquals(nodeInfo, readNodes.first())
} finally {
@@ -117,14 +115,13 @@ class NodeInfoWatcherTest {
nodeInfoPath.createDirectories()
// Start polling with an empty folder.
- val subscription = nodeInfoWatcher.nodeInfoUpdates()
- .subscribe(testSubscriber)
+ val subscription = nodeInfoWatcher.nodeInfoUpdates().subscribe(testSubscriber)
try {
// Ensure the watch service is started.
advanceTime()
// Check no nodeInfos are read.
assertEquals(0, testSubscriber.valueCount)
- createNodeInfoFileInPath(nodeInfo)
+ createNodeInfoFileInPath()
advanceTime()
@@ -143,8 +140,7 @@ class NodeInfoWatcherTest {
}
// Write a nodeInfo under the right path.
- private fun createNodeInfoFileInPath(nodeInfo: NodeInfo) {
- val signedNodeInfo = SignedData(nodeInfo.serialize(), keyManagementService.sign(nodeInfo.serialize().bytes, nodeInfo.legalIdentities.first().owningKey))
+ private fun createNodeInfoFileInPath() {
NodeInfoWatcher.saveToFile(nodeInfoPath, signedNodeInfo)
}
}
diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt
index 34779ac2c3..3465caebdc 100644
--- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt
@@ -14,14 +14,13 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
-import net.corda.testing.ALICE_NAME
import net.corda.testing.IntegrationTest
import net.corda.testing.IntegrationTestSchemas
+import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.testing.driver.PortAllocation
import net.corda.testing.node.internal.internalDriver
-import net.corda.testing.node.network.NetworkMapServer
-import net.corda.testing.toDatabaseSchemaName
+import net.corda.testing.node.internal.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.pkcs.PKCS10CertificationRequest
@@ -29,6 +28,7 @@ import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After
import org.junit.Before
import org.junit.ClassRule
+import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.InputStream
@@ -47,7 +47,9 @@ class NodeRegistrationTest : IntegrationTest() {
@ClassRule @JvmField
val databaseSchemas = IntegrationTestSchemas("Alice")
}
-
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule(true)
private val portAllocation = PortAllocation.Incremental(13000)
private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate()
private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
@@ -57,7 +59,7 @@ class NodeRegistrationTest : IntegrationTest() {
@Before
fun startServer() {
- server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler)
+ server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), rootCertAndKeyPair, registrationHandler)
serverHostAndPort = server.start()
}
@@ -74,7 +76,8 @@ class NodeRegistrationTest : IntegrationTest() {
internalDriver(
portAllocation = portAllocation,
notarySpecs = emptyList(),
- compatibilityZone = compatibilityZone
+ compatibilityZone = compatibilityZone,
+ initialiseSerialization = false
) {
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
assertThat(registrationHandler.idsPolled).contains("Alice")
diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt
index 94eb96a7a8..f8da5302a4 100644
--- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt
@@ -8,7 +8,6 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.*
-import net.corda.testing.messaging.SimpleMQClient
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt
index d4cd7ca4e4..eab85f937a 100644
--- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt
@@ -1,7 +1,6 @@
package net.corda.services.messaging
import net.corda.nodeapi.internal.config.User
-import net.corda.testing.messaging.SimpleMQClient
import org.junit.Test
/**
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 561fdea008..cc146d78d2 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
@@ -26,7 +26,6 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREF
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
-import net.corda.testing.messaging.SimpleMQClient
import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.node.startFlow
import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException
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 5dfa2963b1..4841d20750 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
@@ -101,13 +101,10 @@ class P2PMessagingTest : IntegrationTest() {
// Restart the node and expect a response
val aliceRestarted = startAlice()
-
- val responseFuture = openFuture()
- aliceRestarted.network.runOnNextMessage("test.response") {
+ val responseFuture = openFuture() aliceRestarted.network.runOnNextMessage("test.response") {
responseFuture.set(it.data.deserialize())
}
val response = responseFuture.getOrThrow()
-
assertThat(crashingNodes.requestsReceived.get()).isGreaterThan(numberOfRequestsReceived)
assertThat(response).isEqualTo(responseMessage)
}
diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt
similarity index 98%
rename from testing/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt
rename to node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt
index 235692b111..4257e7e3c0 100644
--- a/testing/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt
+++ b/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt
@@ -1,4 +1,4 @@
-package net.corda.testing.messaging
+package net.corda.services.messaging
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.internal.nodeSerializationEnv
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 790ccfbf2d..91e9ae6afb 100644
--- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
@@ -10,6 +10,8 @@ import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext
+import net.corda.core.crypto.CompositeKey
+import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.crypto.newSecureRandom
@@ -33,10 +35,10 @@ import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
+import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
-import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.services.api.*
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NodeConfiguration
@@ -60,7 +62,10 @@ import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.NetworkParameters
import net.corda.nodeapi.internal.persistence.SchemaMigration
+import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.*
+import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
+import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
@@ -139,11 +144,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
protected lateinit var network: MessagingService
protected val runOnStop = ArrayList<() -> Any?>()
protected val _nodeReadyFuture = openFuture()
- protected val networkMapClient: NetworkMapClient? by lazy {
- configuration.compatibilityZoneURL?.let {
- NetworkMapClient(it, services.identityService.trustRoot)
- }
- }
+ protected var networkMapClient: NetworkMapClient? = null
lateinit var securityManager: RPCSecurityManager get
@@ -178,6 +179,14 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
validateKeystore()
}
+ private inline fun signNodeInfo(nodeInfo: NodeInfo, sign: (PublicKey, SerializedBytes) -> DigitalSignature): SignedNodeInfo {
+ // For now we assume the node has only one identity (excluding any composite ones)
+ val owningKey = nodeInfo.legalIdentities.single { it.owningKey !is CompositeKey }.owningKey
+ val serialised = nodeInfo.serialize()
+ val signature = sign(owningKey, serialised)
+ return SignedNodeInfo(serialised, listOf(signature))
+ }
+
open fun generateNodeInfo() {
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
@@ -189,11 +198,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// a code smell.
val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList())
val (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair)
- val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
- val serialisedNodeInfo = info.serialize()
- val signature = identityKeypair.sign(serialisedNodeInfo)
- // TODO: Signed data might not be sufficient for multiple identities, as it only contains one signature.
- NodeInfoWatcher.saveToFile(configuration.baseDirectory, SignedData(serialisedNodeInfo, signature))
+ val signedNodeInfo = signNodeInfo(info) { publicKey, serialised ->
+ val privateKey = keyPairs.single { it.public == publicKey }.private
+ privateKey.sign(serialised.bytes)
+ }
+ NodeInfoWatcher.saveToFile(configuration.baseDirectory, signedNodeInfo)
}
}
@@ -213,10 +222,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
check(started == null) { "Node has already been started" }
log.info("Node starting up ...")
initCertificate()
- readNetworkParameters()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val identityService = makeIdentityService(identity.certificate)
+ networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
+ retrieveNetworkParameters()
// Do all of this in a database transaction so anything that might need a connection has one.
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
@@ -260,16 +270,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
startShell(rpcOps)
Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService)
}
-
val networkMapUpdater = NetworkMapUpdater(services.networkMapCache,
NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)),
- networkMapClient)
+ networkMapClient,
+ networkParameters.serialize().hash)
runOnStop += networkMapUpdater::close
networkMapUpdater.updateNodeInfo(services.myInfo) {
- val serialisedNodeInfo = it.serialize()
- val signature = services.keyManagementService.sign(serialisedNodeInfo.bytes, it.legalIdentities.first().owningKey)
- SignedData(serialisedNodeInfo, signature)
+ signNodeInfo(it) { publicKey, serialised ->
+ services.keyManagementService.sign(serialised.bytes, publicKey).withoutKey()
+ }
}
networkMapUpdater.subscribeToNetworkMap()
@@ -658,11 +668,31 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return PersistentKeyManagementService(identityService, keyPairs)
}
- private fun readNetworkParameters() {
- val file = configuration.baseDirectory / "network-parameters"
- networkParameters = file.readAll().deserialize>().verified()
- log.info(networkParameters.toString())
- check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" }
+ private fun retrieveNetworkParameters() {
+ val networkParamsFile = configuration.baseDirectory.list { paths ->
+ paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().orElse(null)
+ }
+
+ networkParameters = if (networkParamsFile != null) {
+ networkParamsFile.readAll().deserialize>().verified()
+ } else {
+ log.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
+ val networkMapClient = checkNotNull(networkMapClient) {
+ "Node hasn't been configured to connect to a network map from which to get the network parameters"
+ }
+ val (networkMap, _) = networkMapClient.getNetworkMap()
+ val signedParams = checkNotNull(networkMapClient.getNetworkParameter(networkMap.networkParameterHash)) {
+ "Failed loading network parameters from network map server"
+ }
+ val verifiedParams = signedParams.verified()
+ signedParams.serialize().open().copyTo(configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
+ verifiedParams
+ }
+
+ log.info("Loaded network parameters: $networkParameters")
+ check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) {
+ "Node's platform version is lower than network's required minimumPlatformVersion"
+ }
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
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 df43650e8e..db5b2eb12e 100644
--- a/node/src/main/kotlin/net/corda/node/internal/Node.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt
@@ -144,9 +144,9 @@ open class Node(configuration: NodeConfiguration,
val advertisedAddress = info.addresses.single()
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
- rpcMessagingClient = RPCMessagingClient(configuration, serverAddress)
+ rpcMessagingClient = RPCMessagingClient(configuration, serverAddress, networkParameters.maxMessageSize)
verifierMessagingClient = when (configuration.verifierType) {
- VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics)
+ VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics, networkParameters.maxMessageSize)
VerifierType.InMemory -> null
}
return P2PMessagingClient(
@@ -156,12 +156,13 @@ open class Node(configuration: NodeConfiguration,
info.legalIdentities[0].owningKey,
serverThread,
database,
- advertisedAddress)
+ advertisedAddress,
+ networkParameters.maxMessageSize)
}
private fun makeLocalMessageBroker(): NetworkHostAndPort {
with(configuration) {
- messageBroker = ArtemisMessagingServer(this, p2pAddress.port, rpcAddress?.port, services.networkMapCache, securityManager)
+ messageBroker = ArtemisMessagingServer(this, p2pAddress.port, rpcAddress?.port, services.networkMapCache, securityManager, networkParameters.maxMessageSize)
return NetworkHostAndPort("localhost", p2pAddress.port)
}
}
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/ConnectionStateMachine.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/ConnectionStateMachine.kt
new file mode 100644
index 0000000000..8ae8acda99
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/ConnectionStateMachine.kt
@@ -0,0 +1,483 @@
+package net.corda.node.internal.protonwrapper.engine
+
+import io.netty.buffer.ByteBuf
+import io.netty.buffer.PooledByteBufAllocator
+import io.netty.buffer.Unpooled
+import io.netty.channel.Channel
+import io.netty.channel.ChannelHandlerContext
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.debug
+import net.corda.node.internal.protonwrapper.messages.MessageStatus
+import net.corda.node.internal.protonwrapper.messages.impl.ReceivedMessageImpl
+import net.corda.node.internal.protonwrapper.messages.impl.SendableMessageImpl
+import org.apache.qpid.proton.Proton
+import org.apache.qpid.proton.amqp.Binary
+import org.apache.qpid.proton.amqp.Symbol
+import org.apache.qpid.proton.amqp.messaging.*
+import org.apache.qpid.proton.amqp.messaging.Properties
+import org.apache.qpid.proton.amqp.messaging.Target
+import org.apache.qpid.proton.amqp.transaction.Coordinator
+import org.apache.qpid.proton.amqp.transport.ErrorCondition
+import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode
+import org.apache.qpid.proton.amqp.transport.SenderSettleMode
+import org.apache.qpid.proton.engine.*
+import org.apache.qpid.proton.message.Message
+import org.apache.qpid.proton.message.ProtonJMessage
+import org.slf4j.LoggerFactory
+import java.net.InetSocketAddress
+import java.nio.ByteBuffer
+import java.util.*
+
+/**
+ * This ConnectionStateMachine class handles the events generated by the proton-j library to track
+ * various logical connection, transport and link objects and to drive packet processing.
+ * It is single threaded per physical SSL connection just like the proton-j library,
+ * but this threading lock is managed by the EventProcessor class that calls this.
+ * It ultimately posts application packets to/from from the netty transport pipeline.
+ */
+internal class ConnectionStateMachine(serverMode: Boolean,
+ collector: Collector,
+ private val localLegalName: String,
+ private val remoteLegalName: String,
+ userName: String?,
+ password: String?) : BaseHandler() {
+ companion object {
+ private const val IDLE_TIMEOUT = 10000
+ }
+
+ val connection: Connection
+ private val log = LoggerFactory.getLogger(localLegalName)
+ private val transport: Transport
+ private val id = UUID.randomUUID().toString()
+ private var session: Session? = null
+ private val messageQueues = mutableMapOf>()
+ private val unackedQueue = LinkedList()
+ private val receivers = mutableMapOf()
+ private val senders = mutableMapOf()
+ private var tagId: Int = 0
+
+ init {
+ connection = Engine.connection()
+ connection.container = "CORDA:$id"
+ transport = Engine.transport()
+ transport.idleTimeout = IDLE_TIMEOUT
+ transport.context = connection
+ transport.setEmitFlowEventOnSend(true)
+ connection.collect(collector)
+ val sasl = transport.sasl()
+ if (userName != null) {
+ //TODO This handshake is required for our queue permission logic in Artemis
+ sasl.setMechanisms("PLAIN")
+ if (serverMode) {
+ sasl.server()
+ sasl.done(Sasl.PN_SASL_OK)
+ } else {
+ sasl.plain(userName, password)
+ sasl.client()
+ }
+ } else {
+ sasl.setMechanisms("ANONYMOUS")
+ if (serverMode) {
+ sasl.server()
+ sasl.done(Sasl.PN_SASL_OK)
+ } else {
+ sasl.client()
+ }
+ }
+ transport.bind(connection)
+ if (!serverMode) {
+ connection.open()
+ }
+ }
+
+ override fun onConnectionInit(event: Event) {
+ val connection = event.connection
+ log.debug { "Connection init $connection" }
+ }
+
+ override fun onConnectionLocalOpen(event: Event) {
+ val connection = event.connection
+ log.info("Connection local open $connection")
+ val session = connection.session()
+ session.open()
+ this.session = session
+ for (target in messageQueues.keys) {
+ getSender(target)
+ }
+ }
+
+ override fun onConnectionLocalClose(event: Event) {
+ val connection = event.connection
+ log.info("Connection local close $connection")
+ connection.close()
+ connection.free()
+ }
+
+ override fun onConnectionUnbound(event: Event) {
+ if (event.connection == this.connection) {
+ val channel = connection.context as? Channel
+ if (channel != null) {
+ if (channel.isActive) {
+ channel.close()
+ }
+ }
+ }
+ }
+
+ override fun onConnectionFinal(event: Event) {
+ val connection = event.connection
+ log.debug { "Connection final $connection" }
+ if (connection == this.connection) {
+ this.connection.context = null
+ for (queue in messageQueues.values) {
+ // clear any dead messages
+ while (true) {
+ val msg = queue.poll()
+ if (msg != null) {
+ msg.doComplete(MessageStatus.Rejected)
+ msg.release()
+ } else {
+ break
+ }
+ }
+ }
+ messageQueues.clear()
+ while (true) {
+ val msg = unackedQueue.poll()
+ if (msg != null) {
+ msg.doComplete(MessageStatus.Rejected)
+ msg.release()
+ } else {
+ break
+ }
+ }
+ // shouldn't happen, but close socket channel now if not already done
+ val channel = connection.context as? Channel
+ if (channel != null && channel.isActive) {
+ channel.close()
+ }
+ // shouldn't happen, but cleanup any stranded items
+ transport.context = null
+ session = null
+ receivers.clear()
+ senders.clear()
+ }
+ }
+
+ override fun onTransportHeadClosed(event: Event) {
+ val transport = event.transport
+ log.debug { "Transport Head Closed $transport" }
+ transport.close_tail()
+ }
+
+ override fun onTransportTailClosed(event: Event) {
+ val transport = event.transport
+ log.debug { "Transport Tail Closed $transport" }
+ transport.close_head()
+ }
+
+ override fun onTransportClosed(event: Event) {
+ val transport = event.transport
+ log.debug { "Transport Closed $transport" }
+ if (transport == this.transport) {
+ transport.unbind()
+ transport.free()
+ transport.context = null
+ }
+ }
+
+ override fun onTransportError(event: Event) {
+ val transport = event.transport
+ log.info("Transport Error $transport")
+ val condition = event.transport.condition
+ if (condition != null) {
+ log.info("Error: ${condition.description}")
+ } else {
+ log.info("Error (no description returned).")
+ }
+ }
+
+ override fun onTransport(event: Event) {
+ val transport = event.transport
+ log.debug { "Transport $transport" }
+ onTransportInternal(transport)
+ }
+
+ private fun onTransportInternal(transport: Transport) {
+ if (!transport.isClosed) {
+ val pending = transport.pending() // Note this drives frame generation, which the susbsequent writes push to the socket
+ if (pending > 0) {
+ val connection = transport.context as? Connection
+ val channel = connection?.context as? Channel
+ channel?.writeAndFlush(transport)
+ }
+ }
+ }
+
+ override fun onSessionInit(event: Event) {
+ val session = event.session
+ log.debug { "Session init $session" }
+ }
+
+ override fun onSessionLocalOpen(event: Event) {
+ val session = event.session
+ log.debug { "Session local open $session" }
+ }
+
+ private fun getSender(target: String): Sender {
+ if (!senders.containsKey(target)) {
+ val sender = session!!.sender(UUID.randomUUID().toString())
+ sender.source = Source().apply {
+ address = target
+ dynamic = false
+ durable = TerminusDurability.NONE
+ }
+ sender.target = Target().apply {
+ address = target
+ dynamic = false
+ durable = TerminusDurability.UNSETTLED_STATE
+ }
+ sender.senderSettleMode = SenderSettleMode.UNSETTLED
+ sender.receiverSettleMode = ReceiverSettleMode.FIRST
+ senders[target] = sender
+ sender.open()
+ }
+ return senders[target]!!
+ }
+
+ override fun onSessionLocalClose(event: Event) {
+ val session = event.session
+ log.debug { "Session local close $session" }
+ session.close()
+ session.free()
+ }
+
+ override fun onSessionFinal(event: Event) {
+ val session = event.session
+ log.debug { "Session final $session" }
+ if (session == this.session) {
+ this.session = null
+ }
+ }
+
+ override fun onLinkLocalOpen(event: Event) {
+ val link = event.link
+ if (link is Sender) {
+ log.debug { "Sender Link local open ${link.name} ${link.source} ${link.target}" }
+ senders[link.target.address] = link
+ transmitMessages(link)
+ }
+ if (link is Receiver) {
+ log.debug { "Receiver Link local open ${link.name} ${link.source} ${link.target}" }
+ receivers[link.target.address] = link
+ }
+ }
+
+ override fun onLinkRemoteOpen(event: Event) {
+ val link = event.link
+ if (link is Receiver) {
+ if (link.remoteTarget is Coordinator) {
+ log.debug { "Coordinator link received" }
+ }
+ }
+ }
+
+ override fun onLinkFinal(event: Event) {
+ val link = event.link
+ if (link is Sender) {
+ log.debug { "Sender Link final ${link.name} ${link.source} ${link.target}" }
+ senders.remove(link.target.address)
+ }
+ if (link is Receiver) {
+ log.debug { "Receiver Link final ${link.name} ${link.source} ${link.target}" }
+ receivers.remove(link.target.address)
+ }
+ }
+
+ override fun onLinkFlow(event: Event) {
+ val link = event.link
+ if (link is Sender) {
+ log.debug { "Sender Flow event: ${link.name} ${link.source} ${link.target}" }
+ if (senders.containsKey(link.target.address)) {
+ transmitMessages(link)
+ }
+ } else if (link is Receiver) {
+ log.debug { "Receiver Flow event: ${link.name} ${link.source} ${link.target}" }
+ }
+ }
+
+ fun processTransport() {
+ onTransportInternal(transport)
+ }
+
+ private fun transmitMessages(sender: Sender) {
+ val messageQueue = messageQueues.getOrPut(sender.target.address, { LinkedList() })
+ while (sender.credit > 0) {
+ log.debug { "Sender credit: ${sender.credit}" }
+ val nextMessage = messageQueue.poll()
+ if (nextMessage != null) {
+ try {
+ val messageBuf = nextMessage.buf!!
+ val buf = ByteBuffer.allocate(4)
+ buf.putInt(tagId++)
+ val delivery = sender.delivery(buf.array())
+ delivery.context = nextMessage
+ sender.send(messageBuf.array(), messageBuf.arrayOffset() + messageBuf.readerIndex(), messageBuf.readableBytes())
+ nextMessage.status = MessageStatus.Sent
+ log.debug { "Put tag ${javax.xml.bind.DatatypeConverter.printHexBinary(delivery.tag)} on wire uuid: ${nextMessage.applicationProperties["_AMQ_DUPL_ID"]}" }
+ unackedQueue.offer(nextMessage)
+ sender.advance()
+ } finally {
+ nextMessage.release()
+ }
+ } else {
+ break
+ }
+ }
+ }
+
+ override fun onDelivery(event: Event) {
+ val delivery = event.delivery
+ log.debug { "Delivery $delivery" }
+ val link = delivery.link
+ if (link is Receiver) {
+ if (delivery.isReadable && !delivery.isPartial) {
+ val pending = delivery.pending()
+ val amqpMessage = decodeAMQPMessage(pending, link)
+ val payload = (amqpMessage.body as Data).value.array
+ val connection = event.connection
+ val channel = connection?.context as? Channel
+ if (channel != null) {
+ val appProperties = HashMap(amqpMessage.applicationProperties.value)
+ appProperties["_AMQ_VALIDATED_USER"] = remoteLegalName
+ val localAddress = channel.localAddress() as InetSocketAddress
+ val remoteAddress = channel.remoteAddress() as InetSocketAddress
+ val receivedMessage = ReceivedMessageImpl(
+ payload,
+ link.source.address,
+ remoteLegalName,
+ NetworkHostAndPort(localAddress.hostString, localAddress.port),
+ localLegalName,
+ NetworkHostAndPort(remoteAddress.hostString, remoteAddress.port),
+ appProperties,
+ channel,
+ delivery)
+ log.debug { "Full message received uuid: ${appProperties["_AMQ_DUPL_ID"]}" }
+ channel.writeAndFlush(receivedMessage)
+ if (link.current() == delivery) {
+ link.advance()
+ }
+ } else {
+ delivery.disposition(Rejected())
+ delivery.settle()
+ }
+ }
+ } else if (link is Sender) {
+ log.debug { "Sender delivery confirmed tag ${javax.xml.bind.DatatypeConverter.printHexBinary(delivery.tag)}" }
+ val ok = delivery.remotelySettled() && delivery.remoteState == Accepted.getInstance()
+ val sourceMessage = delivery.context as? SendableMessageImpl
+ unackedQueue.remove(sourceMessage)
+ sourceMessage?.doComplete(if (ok) MessageStatus.Acknowledged else MessageStatus.Rejected)
+ delivery.settle()
+ }
+ }
+
+ private fun encodeAMQPMessage(message: ProtonJMessage): ByteBuf {
+ val buffer = PooledByteBufAllocator.DEFAULT.heapBuffer(1500)
+ try {
+ try {
+ message.encode(NettyWritable(buffer))
+ val bytes = ByteArray(buffer.writerIndex())
+ buffer.readBytes(bytes)
+ return Unpooled.wrappedBuffer(bytes)
+ } catch (ex: Exception) {
+ log.error("Unable to encode message as AMQP packet", ex)
+ throw ex
+ }
+ } finally {
+ buffer.release()
+ }
+ }
+
+ private fun encodePayloadBytes(msg: SendableMessageImpl): ByteBuf {
+ val message = Proton.message() as ProtonJMessage
+ message.body = Data(Binary(msg.payload))
+ message.properties = Properties()
+ val appProperties = HashMap(msg.applicationProperties)
+ //TODO We shouldn't have to do this, but Artemis Server doesn't set the header on AMQP packets.
+ // Fortunately, when we are bridge to bridge/bridge to float we can authenticate links there.
+ appProperties["_AMQ_VALIDATED_USER"] = localLegalName
+ message.applicationProperties = ApplicationProperties(appProperties)
+ return encodeAMQPMessage(message)
+ }
+
+ private fun decodeAMQPMessage(pending: Int, link: Receiver): Message {
+ val msgBuf = PooledByteBufAllocator.DEFAULT.heapBuffer(pending)
+ try {
+ link.recv(NettyWritable(msgBuf))
+ val amqpMessage = Proton.message()
+ amqpMessage.decode(msgBuf.array(), msgBuf.arrayOffset() + msgBuf.readerIndex(), msgBuf.readableBytes())
+ return amqpMessage
+ } finally {
+ msgBuf.release()
+ }
+ }
+
+ fun transportWriteMessage(msg: SendableMessageImpl) {
+ log.debug { "Queue application message write uuid: ${msg.applicationProperties["_AMQ_DUPL_ID"]} ${javax.xml.bind.DatatypeConverter.printHexBinary(msg.payload)}" }
+ msg.buf = encodePayloadBytes(msg)
+ val messageQueue = messageQueues.getOrPut(msg.topic, { LinkedList() })
+ messageQueue.offer(msg)
+ if (session != null) {
+ val sender = getSender(msg.topic)
+ transmitMessages(sender)
+ }
+ }
+
+ fun transportProcessInput(msg: ByteBuf) {
+ val source = msg.nioBuffer()
+ try {
+ do {
+ val buffer = transport.inputBuffer
+ val limit = Math.min(buffer.remaining(), source.remaining())
+ val duplicate = source.duplicate()
+ duplicate.limit(source.position() + limit)
+ buffer.put(duplicate)
+ transport.processInput().checkIsOk()
+ source.position(source.position() + limit)
+ } while (source.hasRemaining())
+ } catch (ex: Exception) {
+ val condition = ErrorCondition()
+ condition.condition = Symbol.getSymbol("proton:io")
+ condition.description = ex.message
+ transport.condition = condition
+ transport.close_tail()
+ transport.pop(Math.max(0, transport.pending())) // Force generation of TRANSPORT_HEAD_CLOSE (not in C code)
+ }
+ }
+
+ fun transportProcessOutput(ctx: ChannelHandlerContext) {
+ try {
+ var done = false
+ while (!done) {
+ val toWrite = transport.outputBuffer
+ if (toWrite != null && toWrite.hasRemaining()) {
+ val outbound = ctx.alloc().buffer(toWrite.remaining())
+ outbound.writeBytes(toWrite)
+ ctx.write(outbound)
+ transport.outputConsumed()
+ } else {
+ done = true
+ }
+ }
+ ctx.flush()
+ } catch (ex: Exception) {
+ val condition = ErrorCondition()
+ condition.condition = Symbol.getSymbol("proton:io")
+ condition.description = ex.message
+ transport.condition = condition
+ transport.close_head()
+ transport.pop(Math.max(0, transport.pending())) // Force generation of TRANSPORT_HEAD_CLOSE (not in C code)
+ }
+ }
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/EventProcessor.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/EventProcessor.kt
new file mode 100644
index 0000000000..31c7de7aed
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/EventProcessor.kt
@@ -0,0 +1,136 @@
+package net.corda.node.internal.protonwrapper.engine
+
+import io.netty.buffer.ByteBuf
+import io.netty.channel.Channel
+import io.netty.channel.ChannelHandlerContext
+import net.corda.core.utilities.debug
+import net.corda.node.internal.protonwrapper.messages.MessageStatus
+import net.corda.node.internal.protonwrapper.messages.impl.ReceivedMessageImpl
+import net.corda.node.internal.protonwrapper.messages.impl.SendableMessageImpl
+import org.apache.qpid.proton.Proton
+import org.apache.qpid.proton.amqp.messaging.Accepted
+import org.apache.qpid.proton.amqp.messaging.Rejected
+import org.apache.qpid.proton.amqp.transport.DeliveryState
+import org.apache.qpid.proton.amqp.transport.ErrorCondition
+import org.apache.qpid.proton.engine.*
+import org.apache.qpid.proton.engine.impl.CollectorImpl
+import org.apache.qpid.proton.reactor.FlowController
+import org.apache.qpid.proton.reactor.Handshaker
+import org.slf4j.LoggerFactory
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/**
+ * The EventProcessor class converts calls on the netty scheduler/pipeline
+ * into proton-j engine event calls into the ConnectionStateMachine.
+ * It also registers a couple of standard event processors for the basic connection handshake
+ * and simple sliding window flow control, so that these events don't have to live inside ConnectionStateMachine.
+ * Everything here is single threaded, because the proton-j library has to be run that way.
+ */
+internal class EventProcessor(channel: Channel,
+ serverMode: Boolean,
+ localLegalName: String,
+ remoteLegalName: String,
+ userName: String?,
+ password: String?) : BaseHandler() {
+ companion object {
+ private const val FLOW_WINDOW_SIZE = 10
+ }
+
+ private val log = LoggerFactory.getLogger(localLegalName)
+ private val lock = ReentrantLock()
+ private var pendingExecute: Boolean = false
+ private val executor: ScheduledExecutorService = channel.eventLoop()
+ private val collector = Proton.collector() as CollectorImpl
+ private val handlers = mutableListOf()
+ private val stateMachine: ConnectionStateMachine = ConnectionStateMachine(serverMode,
+ collector,
+ localLegalName,
+ remoteLegalName,
+ userName,
+ password)
+
+ val connection: Connection = stateMachine.connection
+
+ init {
+ addHandler(Handshaker())
+ addHandler(FlowController(FLOW_WINDOW_SIZE))
+ addHandler(stateMachine)
+ connection.context = channel
+ tick(stateMachine.connection)
+ }
+
+ fun addHandler(handler: Handler) = handlers.add(handler)
+
+ private fun popEvent(): Event? {
+ var ev = collector.peek()
+ if (ev != null) {
+ ev = ev.copy() // prevent mutation by collector.pop()
+ collector.pop()
+ }
+ return ev
+ }
+
+ private fun tick(connection: Connection) {
+ lock.withLock {
+ try {
+ if ((connection.localState != EndpointState.CLOSED) && !connection.transport.isClosed) {
+ val now = System.currentTimeMillis()
+ val tickDelay = Math.max(0L, connection.transport.tick(now) - now)
+ executor.schedule({ tick(connection) }, tickDelay, TimeUnit.MILLISECONDS)
+ }
+ } catch (ex: Exception) {
+ connection.transport.close()
+ connection.condition = ErrorCondition()
+ }
+ }
+ }
+
+ fun processEvents() {
+ lock.withLock {
+ pendingExecute = false
+ log.debug { "Process Events" }
+ while (true) {
+ val ev = popEvent() ?: break
+ log.debug { "Process event: $ev" }
+ for (handler in handlers) {
+ handler.handle(ev)
+ }
+ }
+ stateMachine.processTransport()
+ log.debug { "Process Events Done" }
+ }
+ }
+
+ fun processEventsAsync() {
+ lock.withLock {
+ if (!pendingExecute) {
+ pendingExecute = true
+ executor.execute { processEvents() }
+ }
+ }
+ }
+
+ fun close() {
+ if (connection.localState != EndpointState.CLOSED) {
+ connection.close()
+ processEvents()
+ connection.free()
+ processEvents()
+ }
+ }
+
+ fun transportProcessInput(msg: ByteBuf) = lock.withLock { stateMachine.transportProcessInput(msg) }
+
+ fun transportProcessOutput(ctx: ChannelHandlerContext) = lock.withLock { stateMachine.transportProcessOutput(ctx) }
+
+ fun transportWriteMessage(msg: SendableMessageImpl) = lock.withLock { stateMachine.transportWriteMessage(msg) }
+
+ fun complete(completer: ReceivedMessageImpl.MessageCompleter) = lock.withLock {
+ val status: DeliveryState = if (completer.status == MessageStatus.Acknowledged) Accepted.getInstance() else Rejected()
+ completer.delivery.disposition(status)
+ completer.delivery.settle()
+ }
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/NettyWritable.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/NettyWritable.kt
new file mode 100644
index 0000000000..b8e8085273
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/NettyWritable.kt
@@ -0,0 +1,63 @@
+package net.corda.node.internal.protonwrapper.engine
+
+import io.netty.buffer.ByteBuf
+import org.apache.qpid.proton.codec.WritableBuffer
+import java.nio.ByteBuffer
+
+/**
+ * NettyWritable is a utility class allow proton-j encoders to write directly into a
+ * netty ByteBuf, without any need to materialize a ByteArray copy.
+ */
+internal class NettyWritable(val nettyBuffer: ByteBuf) : WritableBuffer {
+ override fun put(b: Byte) {
+ nettyBuffer.writeByte(b.toInt())
+ }
+
+ override fun putFloat(f: Float) {
+ nettyBuffer.writeFloat(f)
+ }
+
+ override fun putDouble(d: Double) {
+ nettyBuffer.writeDouble(d)
+ }
+
+ override fun put(src: ByteArray, offset: Int, length: Int) {
+ nettyBuffer.writeBytes(src, offset, length)
+ }
+
+ override fun putShort(s: Short) {
+ nettyBuffer.writeShort(s.toInt())
+ }
+
+ override fun putInt(i: Int) {
+ nettyBuffer.writeInt(i)
+ }
+
+ override fun putLong(l: Long) {
+ nettyBuffer.writeLong(l)
+ }
+
+ override fun hasRemaining(): Boolean {
+ return nettyBuffer.writerIndex() < nettyBuffer.capacity()
+ }
+
+ override fun remaining(): Int {
+ return nettyBuffer.capacity() - nettyBuffer.writerIndex()
+ }
+
+ override fun position(): Int {
+ return nettyBuffer.writerIndex()
+ }
+
+ override fun position(position: Int) {
+ nettyBuffer.writerIndex(position)
+ }
+
+ override fun put(payload: ByteBuffer) {
+ nettyBuffer.writeBytes(payload)
+ }
+
+ override fun limit(): Int {
+ return nettyBuffer.capacity()
+ }
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ApplicationMessage.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ApplicationMessage.kt
new file mode 100644
index 0000000000..f90c1b172a
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ApplicationMessage.kt
@@ -0,0 +1,14 @@
+package net.corda.node.internal.protonwrapper.messages
+
+import net.corda.core.utilities.NetworkHostAndPort
+
+/**
+ * Represents a common interface for both sendable and received application messages.
+ */
+interface ApplicationMessage {
+ val payload: ByteArray
+ val topic: String
+ val destinationLegalName: String
+ val destinationLink: NetworkHostAndPort
+ val applicationProperties: Map
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/MessageStatus.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/MessageStatus.kt
new file mode 100644
index 0000000000..6b792dd59e
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/MessageStatus.kt
@@ -0,0 +1,11 @@
+package net.corda.node.internal.protonwrapper.messages
+
+/**
+ * The processing state of a message.
+ */
+enum class MessageStatus {
+ Unsent,
+ Sent,
+ Acknowledged,
+ Rejected
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ReceivedMessage.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ReceivedMessage.kt
new file mode 100644
index 0000000000..df4a1fddff
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ReceivedMessage.kt
@@ -0,0 +1,13 @@
+package net.corda.node.internal.protonwrapper.messages
+
+import net.corda.core.utilities.NetworkHostAndPort
+
+/**
+ * An extension of ApplicationMessage that includes origin information.
+ */
+interface ReceivedMessage : ApplicationMessage {
+ val sourceLegalName: String
+ val sourceLink: NetworkHostAndPort
+
+ fun complete(accepted: Boolean)
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/SendableMessage.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/SendableMessage.kt
new file mode 100644
index 0000000000..bc4a405e26
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/SendableMessage.kt
@@ -0,0 +1,10 @@
+package net.corda.node.internal.protonwrapper.messages
+
+import net.corda.core.concurrent.CordaFuture
+
+/**
+ * An extension of ApplicationMessage to allow completion signalling.
+ */
+interface SendableMessage : ApplicationMessage {
+ val onComplete: CordaFuture
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt
new file mode 100644
index 0000000000..1d5f25d59f
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt
@@ -0,0 +1,30 @@
+package net.corda.node.internal.protonwrapper.messages.impl
+
+import io.netty.channel.Channel
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.node.internal.protonwrapper.messages.MessageStatus
+import net.corda.node.internal.protonwrapper.messages.ReceivedMessage
+import org.apache.qpid.proton.engine.Delivery
+
+/**
+ * An internal packet management class that allows tracking of asynchronous acknowledgements
+ * that in turn send Delivery messages back to the originator.
+ */
+internal class ReceivedMessageImpl(override val payload: ByteArray,
+ override val topic: String,
+ override val sourceLegalName: String,
+ override val sourceLink: NetworkHostAndPort,
+ override val destinationLegalName: String,
+ override val destinationLink: NetworkHostAndPort,
+ override val applicationProperties: Map,
+ private val channel: Channel,
+ private val delivery: Delivery) : ReceivedMessage {
+ data class MessageCompleter(val status: MessageStatus, val delivery: Delivery)
+
+ override fun complete(accepted: Boolean) {
+ val status = if (accepted) MessageStatus.Acknowledged else MessageStatus.Rejected
+ channel.writeAndFlush(MessageCompleter(status, delivery))
+ }
+
+ override fun toString(): String = "Received ${String(payload)} $topic"
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/SendableMessageImpl.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/SendableMessageImpl.kt
new file mode 100644
index 0000000000..52dd1ed08f
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/SendableMessageImpl.kt
@@ -0,0 +1,37 @@
+package net.corda.node.internal.protonwrapper.messages.impl
+
+import io.netty.buffer.ByteBuf
+import net.corda.core.concurrent.CordaFuture
+import net.corda.core.internal.concurrent.openFuture
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.node.internal.protonwrapper.messages.MessageStatus
+import net.corda.node.internal.protonwrapper.messages.SendableMessage
+
+/**
+ * An internal packet management class that allows handling of the encoded buffers and
+ * allows registration of an acknowledgement handler when the remote receiver confirms durable storage.
+ */
+internal class SendableMessageImpl(override val payload: ByteArray,
+ override val topic: String,
+ override val destinationLegalName: String,
+ override val destinationLink: NetworkHostAndPort,
+ override val applicationProperties: Map) : SendableMessage {
+ var buf: ByteBuf? = null
+ @Volatile
+ var status: MessageStatus = MessageStatus.Unsent
+
+ private val _onComplete = openFuture()
+ override val onComplete: CordaFuture get() = _onComplete
+
+ fun release() {
+ buf?.release()
+ buf = null
+ }
+
+ fun doComplete(status: MessageStatus) {
+ this.status = status
+ _onComplete.set(status)
+ }
+
+ override fun toString(): String = "Sendable ${String(payload)} $topic $status"
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt
new file mode 100644
index 0000000000..0f9e6cd607
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt
@@ -0,0 +1,156 @@
+package net.corda.node.internal.protonwrapper.netty
+
+import io.netty.buffer.ByteBuf
+import io.netty.channel.ChannelDuplexHandler
+import io.netty.channel.ChannelHandlerContext
+import io.netty.channel.ChannelPromise
+import io.netty.channel.socket.SocketChannel
+import io.netty.handler.ssl.SslHandler
+import io.netty.handler.ssl.SslHandshakeCompletionEvent
+import io.netty.util.ReferenceCountUtil
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.internal.toX509CertHolder
+import net.corda.core.utilities.debug
+import net.corda.node.internal.protonwrapper.engine.EventProcessor
+import net.corda.node.internal.protonwrapper.messages.ReceivedMessage
+import net.corda.node.internal.protonwrapper.messages.impl.ReceivedMessageImpl
+import net.corda.node.internal.protonwrapper.messages.impl.SendableMessageImpl
+import org.apache.qpid.proton.engine.ProtonJTransport
+import org.apache.qpid.proton.engine.Transport
+import org.apache.qpid.proton.engine.impl.ProtocolTracer
+import org.apache.qpid.proton.framing.TransportFrame
+import org.bouncycastle.cert.X509CertificateHolder
+import org.slf4j.LoggerFactory
+import java.net.InetSocketAddress
+
+/**
+ * An instance of AMQPChannelHandler sits inside the netty pipeline and controls the socket level lifecycle.
+ * It also add some extra checks to the SSL handshake to support our non-standard certificate checks of legal identity.
+ * When a valid SSL connections is made then it initialises a proton-j engine instance to handle the protocol layer.
+ */
+internal class AMQPChannelHandler(private val serverMode: Boolean,
+ private val allowedRemoteLegalNames: Set?,
+ private val userName: String?,
+ private val password: String?,
+ private val trace: Boolean,
+ private val onOpen: (Pair) -> Unit,
+ private val onClose: (Pair) -> Unit,
+ private val onReceive: (ReceivedMessage) -> Unit) : ChannelDuplexHandler() {
+ private val log = LoggerFactory.getLogger(allowedRemoteLegalNames?.firstOrNull()?.toString() ?: "AMQPChannelHandler")
+ private lateinit var remoteAddress: InetSocketAddress
+ private lateinit var localCert: X509CertificateHolder
+ private lateinit var remoteCert: X509CertificateHolder
+ private var eventProcessor: EventProcessor? = null
+
+ override fun channelActive(ctx: ChannelHandlerContext) {
+ val ch = ctx.channel()
+ remoteAddress = ch.remoteAddress() as InetSocketAddress
+ val localAddress = ch.localAddress() as InetSocketAddress
+ log.info("New client connection ${ch.id()} from ${remoteAddress} to ${localAddress}")
+ }
+
+ private fun createAMQPEngine(ctx: ChannelHandlerContext) {
+ val ch = ctx.channel()
+ eventProcessor = EventProcessor(ch, serverMode, localCert.subject.toString(), remoteCert.subject.toString(), userName, password)
+ val connection = eventProcessor!!.connection
+ val transport = connection.transport as ProtonJTransport
+ if (trace) {
+ transport.protocolTracer = object : ProtocolTracer {
+ override fun sentFrame(transportFrame: TransportFrame) {
+ log.info("${transportFrame.body}")
+ }
+
+ override fun receivedFrame(transportFrame: TransportFrame) {
+ log.info("${transportFrame.body}")
+ }
+ }
+ }
+ ctx.fireChannelActive()
+ eventProcessor!!.processEventsAsync()
+ }
+
+ override fun channelInactive(ctx: ChannelHandlerContext) {
+ val ch = ctx.channel()
+ log.info("Closed client connection ${ch.id()} from ${remoteAddress} to ${ch.localAddress()}")
+ onClose(Pair(ch as SocketChannel, ConnectionChange(remoteAddress, null, false)))
+ eventProcessor?.close()
+ ctx.fireChannelInactive()
+ }
+
+ override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
+ if (evt is SslHandshakeCompletionEvent) {
+ if (evt.isSuccess) {
+ val sslHandler = ctx.pipeline().get(SslHandler::class.java)
+ localCert = sslHandler.engine().session.localCertificates.first().toX509CertHolder()
+ remoteCert = sslHandler.engine().session.peerCertificates.first().toX509CertHolder()
+ try {
+ val remoteX500Name = CordaX500Name.parse(remoteCert.subject.toString())
+ require(allowedRemoteLegalNames == null || remoteX500Name in allowedRemoteLegalNames)
+ log.info("handshake completed subject: ${remoteX500Name}")
+ } catch (ex: IllegalArgumentException) {
+ log.error("Invalid certificate subject", ex)
+ ctx.close()
+ return
+ }
+ createAMQPEngine(ctx)
+ onOpen(Pair(ctx.channel() as SocketChannel, ConnectionChange(remoteAddress, remoteCert, true)))
+ } else {
+ log.error("Handshake failure $evt")
+ ctx.close()
+ }
+ }
+ }
+
+ override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
+ try {
+ log.debug { "Received $msg" }
+ if (msg is ByteBuf) {
+ eventProcessor!!.transportProcessInput(msg)
+ }
+ } finally {
+ ReferenceCountUtil.release(msg)
+ }
+ eventProcessor!!.processEventsAsync()
+ }
+
+ override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
+ try {
+ try {
+ log.debug { "Sent $msg" }
+ when (msg) {
+ // Transfers application packet into the AMQP engine.
+ is SendableMessageImpl -> {
+ val inetAddress = InetSocketAddress(msg.destinationLink.host, msg.destinationLink.port)
+ require(inetAddress == remoteAddress) {
+ "Message for incorrect endpoint"
+ }
+ require(CordaX500Name.parse(msg.destinationLegalName) == CordaX500Name.parse(remoteCert.subject.toString())) {
+ "Message for incorrect legal identity"
+ }
+ log.debug { "channel write ${msg.applicationProperties["_AMQ_DUPL_ID"]}" }
+ eventProcessor!!.transportWriteMessage(msg)
+ }
+ // A received AMQP packet has been completed and this self-posted packet will be signalled out to the
+ // external application.
+ is ReceivedMessage -> {
+ onReceive(msg)
+ }
+ // A general self-posted event that triggers creation of AMQP frames when required.
+ is Transport -> {
+ eventProcessor!!.transportProcessOutput(ctx)
+ }
+ // A self-posted event that forwards status updates for delivered packets to the application.
+ is ReceivedMessageImpl.MessageCompleter -> {
+ eventProcessor!!.complete(msg)
+ }
+ }
+ } catch (ex: Exception) {
+ log.error("Error in AMQP write processing", ex)
+ throw ex
+ }
+ } finally {
+ ReferenceCountUtil.release(msg)
+ }
+ eventProcessor!!.processEventsAsync()
+ }
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPClient.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPClient.kt
new file mode 100644
index 0000000000..761248797f
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPClient.kt
@@ -0,0 +1,194 @@
+package net.corda.node.internal.protonwrapper.netty
+
+import io.netty.bootstrap.Bootstrap
+import io.netty.channel.*
+import io.netty.channel.nio.NioEventLoopGroup
+import io.netty.channel.socket.SocketChannel
+import io.netty.channel.socket.nio.NioSocketChannel
+import io.netty.handler.logging.LogLevel
+import io.netty.handler.logging.LoggingHandler
+import io.netty.util.internal.logging.InternalLoggerFactory
+import io.netty.util.internal.logging.Slf4JLoggerFactory
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.contextLogger
+import net.corda.node.internal.protonwrapper.messages.ReceivedMessage
+import net.corda.node.internal.protonwrapper.messages.SendableMessage
+import net.corda.node.internal.protonwrapper.messages.impl.SendableMessageImpl
+import rx.Observable
+import rx.subjects.PublishSubject
+import java.security.KeyStore
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.ReentrantLock
+import javax.net.ssl.KeyManagerFactory
+import javax.net.ssl.TrustManagerFactory
+import kotlin.concurrent.withLock
+
+/**
+ * The AMQPClient creates a connection initiator that will try to connect in a round-robin fashion
+ * to the first open SSL socket. It will keep retrying until it is stopped.
+ * To allow thread resource control it can accept a shared thread pool as constructor input,
+ * otherwise it creates a self-contained Netty thraed pool and socket objects.
+ * Once connected it can accept application packets to send via the AMQP protocol.
+ */
+class AMQPClient(val targets: List,
+ val allowedRemoteLegalNames: Set,
+ private val userName: String?,
+ private val password: String?,
+ private val keyStore: KeyStore,
+ private val keyStorePrivateKeyPassword: String,
+ private val trustStore: KeyStore,
+ private val trace: Boolean = false,
+ private val sharedThreadPool: EventLoopGroup? = null) : AutoCloseable {
+ companion object {
+ init {
+ InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE)
+ }
+
+ val log = contextLogger()
+ const val RETRY_INTERVAL = 1000L
+ const val NUM_CLIENT_THREADS = 2
+ }
+
+ private val lock = ReentrantLock()
+ @Volatile
+ private var stopping: Boolean = false
+ private var workerGroup: EventLoopGroup? = null
+ @Volatile
+ private var clientChannel: Channel? = null
+ // Offset into the list of targets, so that we can implement round-robin reconnect logic.
+ private var targetIndex = 0
+ private var currentTarget: NetworkHostAndPort = targets.first()
+
+ private val connectListener = object : ChannelFutureListener {
+ override fun operationComplete(future: ChannelFuture) {
+ if (!future.isSuccess) {
+ log.info("Failed to connect to $currentTarget")
+
+ if (!stopping) {
+ workerGroup?.schedule({
+ log.info("Retry connect to $currentTarget")
+ targetIndex = (targetIndex + 1).rem(targets.size)
+ restart()
+ }, RETRY_INTERVAL, TimeUnit.MILLISECONDS)
+ }
+ } else {
+ log.info("Connected to $currentTarget")
+ // Connection established successfully
+ clientChannel = future.channel()
+ clientChannel?.closeFuture()?.addListener(closeListener)
+ }
+ }
+ }
+
+ private val closeListener = object : ChannelFutureListener {
+ override fun operationComplete(future: ChannelFuture) {
+ log.info("Disconnected from $currentTarget")
+ future.channel()?.disconnect()
+ clientChannel = null
+ if (!stopping) {
+ workerGroup?.schedule({
+ log.info("Retry connect")
+ targetIndex = (targetIndex + 1).rem(targets.size)
+ restart()
+ }, RETRY_INTERVAL, TimeUnit.MILLISECONDS)
+ }
+ }
+ }
+
+ private class ClientChannelInitializer(val parent: AMQPClient) : ChannelInitializer() {
+ private val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
+ private val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
+
+ init {
+ keyManagerFactory.init(parent.keyStore, parent.keyStorePrivateKeyPassword.toCharArray())
+ trustManagerFactory.init(parent.trustStore)
+ }
+
+ override fun initChannel(ch: SocketChannel) {
+ val pipeline = ch.pipeline()
+ val handler = createClientSslHelper(parent.currentTarget, keyManagerFactory, trustManagerFactory)
+ pipeline.addLast("sslHandler", handler)
+ if (parent.trace) pipeline.addLast("logger", LoggingHandler(LogLevel.INFO))
+ pipeline.addLast(AMQPChannelHandler(false,
+ parent.allowedRemoteLegalNames,
+ parent.userName,
+ parent.password,
+ parent.trace,
+ { parent._onConnection.onNext(it.second) },
+ { parent._onConnection.onNext(it.second) },
+ { rcv -> parent._onReceive.onNext(rcv) }))
+ }
+ }
+
+ fun start() {
+ lock.withLock {
+ log.info("connect to: $currentTarget")
+ workerGroup = sharedThreadPool ?: NioEventLoopGroup(NUM_CLIENT_THREADS)
+ restart()
+ }
+ }
+
+ private fun restart() {
+ val bootstrap = Bootstrap()
+ // TODO Needs more configuration control when we profile. e.g. to use EPOLL on Linux
+ bootstrap.group(workerGroup).
+ channel(NioSocketChannel::class.java).
+ handler(ClientChannelInitializer(this))
+ currentTarget = targets[targetIndex]
+ val clientFuture = bootstrap.connect(currentTarget.host, currentTarget.port)
+ clientFuture.addListener(connectListener)
+ }
+
+ fun stop() {
+ lock.withLock {
+ log.info("disconnect from: $currentTarget")
+ stopping = true
+ try {
+ if (sharedThreadPool == null) {
+ workerGroup?.shutdownGracefully()
+ workerGroup?.terminationFuture()?.sync()
+ } else {
+ clientChannel?.close()?.sync()
+ }
+ clientChannel = null
+ workerGroup = null
+ } finally {
+ stopping = false
+ }
+ log.info("stopped connection to $currentTarget")
+ }
+ }
+
+ override fun close() = stop()
+
+ val connected: Boolean
+ get() {
+ val channel = lock.withLock { clientChannel }
+ return channel?.isActive ?: false
+ }
+
+ fun createMessage(payload: ByteArray,
+ topic: String,
+ destinationLegalName: String,
+ properties: Map): SendableMessage {
+ return SendableMessageImpl(payload, topic, destinationLegalName, currentTarget, properties)
+ }
+
+ fun write(msg: SendableMessage) {
+ val channel = clientChannel
+ if (channel == null) {
+ throw IllegalStateException("Connection to $targets not active")
+ } else {
+ channel.writeAndFlush(msg)
+ }
+ }
+
+ private val _onReceive = PublishSubject.create().toSerialized()
+ val onReceive: Observable
+ get() = _onReceive
+
+ private val _onConnection = PublishSubject.create().toSerialized()
+ val onConnection: Observable
+ get() = _onConnection
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPServer.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPServer.kt
new file mode 100644
index 0000000000..6398a6776b
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPServer.kt
@@ -0,0 +1,187 @@
+package net.corda.node.internal.protonwrapper.netty
+
+import io.netty.bootstrap.ServerBootstrap
+import io.netty.channel.Channel
+import io.netty.channel.ChannelInitializer
+import io.netty.channel.ChannelOption
+import io.netty.channel.EventLoopGroup
+import io.netty.channel.nio.NioEventLoopGroup
+import io.netty.channel.socket.SocketChannel
+import io.netty.channel.socket.nio.NioServerSocketChannel
+import io.netty.handler.logging.LogLevel
+import io.netty.handler.logging.LoggingHandler
+import io.netty.util.internal.logging.InternalLoggerFactory
+import io.netty.util.internal.logging.Slf4JLoggerFactory
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.contextLogger
+import net.corda.node.internal.protonwrapper.messages.ReceivedMessage
+import net.corda.node.internal.protonwrapper.messages.SendableMessage
+import net.corda.node.internal.protonwrapper.messages.impl.SendableMessageImpl
+import org.apache.qpid.proton.engine.Delivery
+import rx.Observable
+import rx.subjects.PublishSubject
+import java.net.BindException
+import java.net.InetSocketAddress
+import java.security.KeyStore
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.locks.ReentrantLock
+import javax.net.ssl.KeyManagerFactory
+import javax.net.ssl.TrustManagerFactory
+import kotlin.concurrent.withLock
+
+/**
+ * This create a socket acceptor instance that can receive possibly multiple AMQP connections.
+ * As of now this is not used outside of testing, but in future it will be used for standalone bridging components.
+ */
+class AMQPServer(val hostName: String,
+ val port: Int,
+ private val userName: String?,
+ private val password: String?,
+ private val keyStore: KeyStore,
+ private val keyStorePrivateKeyPassword: String,
+ private val trustStore: KeyStore,
+ private val trace: Boolean = false) : AutoCloseable {
+
+ companion object {
+ init {
+ InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE)
+ }
+
+ private val log = contextLogger()
+ const val NUM_SERVER_THREADS = 4
+ }
+
+ private val lock = ReentrantLock()
+ @Volatile
+ private var stopping: Boolean = false
+ private var bossGroup: EventLoopGroup? = null
+ private var workerGroup: EventLoopGroup? = null
+ private var serverChannel: Channel? = null
+ private val clientChannels = ConcurrentHashMap()
+
+ init {
+ }
+
+ private class ServerChannelInitializer(val parent: AMQPServer) : ChannelInitializer() {
+ private val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
+ private val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
+
+ init {
+ keyManagerFactory.init(parent.keyStore, parent.keyStorePrivateKeyPassword.toCharArray())
+ trustManagerFactory.init(parent.trustStore)
+ }
+
+ override fun initChannel(ch: SocketChannel) {
+ val pipeline = ch.pipeline()
+ val handler = createServerSslHelper(keyManagerFactory, trustManagerFactory)
+ pipeline.addLast("sslHandler", handler)
+ if (parent.trace) pipeline.addLast("logger", LoggingHandler(LogLevel.INFO))
+ pipeline.addLast(AMQPChannelHandler(true,
+ null,
+ parent.userName,
+ parent.password,
+ parent.trace,
+ {
+ parent.clientChannels.put(it.first.remoteAddress(), it.first)
+ parent._onConnection.onNext(it.second)
+ },
+ {
+ parent.clientChannels.remove(it.first.remoteAddress())
+ parent._onConnection.onNext(it.second)
+ },
+ { rcv -> parent._onReceive.onNext(rcv) }))
+ }
+ }
+
+ fun start() {
+ lock.withLock {
+ stop()
+
+ bossGroup = NioEventLoopGroup(1)
+ workerGroup = NioEventLoopGroup(NUM_SERVER_THREADS)
+
+ val server = ServerBootstrap()
+ // TODO Needs more configuration control when we profile. e.g. to use EPOLL on Linux
+ server.group(bossGroup, workerGroup).
+ channel(NioServerSocketChannel::class.java).
+ option(ChannelOption.SO_BACKLOG, 100).
+ handler(LoggingHandler(LogLevel.INFO)).
+ childHandler(ServerChannelInitializer(this))
+
+ log.info("Try to bind $port")
+ val channelFuture = server.bind(hostName, port).sync() // block/throw here as better to know we failed to claim port than carry on
+ if (!channelFuture.isDone || !channelFuture.isSuccess) {
+ throw BindException("Failed to bind port $port")
+ }
+ log.info("Listening on port $port")
+ serverChannel = channelFuture.channel()
+ }
+ }
+
+ fun stop() {
+ lock.withLock {
+ try {
+ stopping = true
+ serverChannel?.apply { close() }
+ serverChannel = null
+
+ workerGroup?.shutdownGracefully()
+ workerGroup?.terminationFuture()?.sync()
+
+ bossGroup?.shutdownGracefully()
+ bossGroup?.terminationFuture()?.sync()
+
+ workerGroup = null
+ bossGroup = null
+ } finally {
+ stopping = false
+ }
+ }
+ }
+
+ override fun close() = stop()
+
+ val listening: Boolean
+ get() {
+ val channel = lock.withLock { serverChannel }
+ return channel?.isActive ?: false
+ }
+
+ fun createMessage(payload: ByteArray,
+ topic: String,
+ destinationLegalName: String,
+ destinationLink: NetworkHostAndPort,
+ properties: Map): SendableMessage {
+ val dest = InetSocketAddress(destinationLink.host, destinationLink.port)
+ require(dest in clientChannels.keys) {
+ "Destination not available"
+ }
+ return SendableMessageImpl(payload, topic, destinationLegalName, destinationLink, properties)
+ }
+
+ fun write(msg: SendableMessage) {
+ val dest = InetSocketAddress(msg.destinationLink.host, msg.destinationLink.port)
+ val channel = clientChannels[dest]
+ if (channel == null) {
+ throw IllegalStateException("Connection to ${msg.destinationLink} not active")
+ } else {
+ channel.writeAndFlush(msg)
+ }
+ }
+
+ fun complete(delivery: Delivery, target: InetSocketAddress) {
+ val channel = clientChannels[target]
+ channel?.apply {
+ writeAndFlush(delivery)
+ }
+ }
+
+ private val _onReceive = PublishSubject.create().toSerialized()
+ val onReceive: Observable
+ get() = _onReceive
+
+ private val _onConnection = PublishSubject.create().toSerialized()
+ val onConnection: Observable
+ get() = _onConnection
+
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt
new file mode 100644
index 0000000000..a576a25d2b
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt
@@ -0,0 +1,6 @@
+package net.corda.node.internal.protonwrapper.netty
+
+import org.bouncycastle.cert.X509CertificateHolder
+import java.net.InetSocketAddress
+
+data class ConnectionChange(val remoteAddress: InetSocketAddress, val remoteCert: X509CertificateHolder?, val connected: Boolean)
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/SSLHelper.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/SSLHelper.kt
new file mode 100644
index 0000000000..7a778d26cf
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/SSLHelper.kt
@@ -0,0 +1,39 @@
+package net.corda.node.internal.protonwrapper.netty
+
+import io.netty.handler.ssl.SslHandler
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.nodeapi.ArtemisTcpTransport
+import java.security.SecureRandom
+import javax.net.ssl.KeyManagerFactory
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManagerFactory
+
+internal fun createClientSslHelper(target: NetworkHostAndPort,
+ keyManagerFactory: KeyManagerFactory,
+ trustManagerFactory: TrustManagerFactory): SslHandler {
+ val sslContext = SSLContext.getInstance("TLS")
+ val keyManagers = keyManagerFactory.keyManagers
+ val trustManagers = trustManagerFactory.trustManagers
+ sslContext.init(keyManagers, trustManagers, SecureRandom())
+ val sslEngine = sslContext.createSSLEngine(target.host, target.port)
+ sslEngine.useClientMode = true
+ sslEngine.enabledProtocols = ArtemisTcpTransport.TLS_VERSIONS.toTypedArray()
+ sslEngine.enabledCipherSuites = ArtemisTcpTransport.CIPHER_SUITES.toTypedArray()
+ sslEngine.enableSessionCreation = true
+ return SslHandler(sslEngine)
+}
+
+internal fun createServerSslHelper(keyManagerFactory: KeyManagerFactory,
+ trustManagerFactory: TrustManagerFactory): SslHandler {
+ val sslContext = SSLContext.getInstance("TLS")
+ val keyManagers = keyManagerFactory.keyManagers
+ val trustManagers = trustManagerFactory.trustManagers
+ sslContext.init(keyManagers, trustManagers, SecureRandom())
+ val sslEngine = sslContext.createSSLEngine()
+ sslEngine.useClientMode = false
+ sslEngine.needClientAuth = true
+ sslEngine.enabledProtocols = ArtemisTcpTransport.TLS_VERSIONS.toTypedArray()
+ sslEngine.enabledCipherSuites = ArtemisTcpTransport.CIPHER_SUITES.toTypedArray()
+ sslEngine.enableSessionCreation = true
+ return SslHandler(sslEngine)
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManager.kt b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManager.kt
index dafa069833..4307df44e0 100644
--- a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManager.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManager.kt
@@ -34,7 +34,7 @@ fun RPCSecurityManager.tryAuthenticate(principal: String, password: Password): A
password.use {
return try {
authenticate(principal, password)
- } catch (e: AuthenticationException) {
+ } catch (e: FailedLoginException) {
null
}
}
diff --git a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt
index add690620e..e667d673b7 100644
--- a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt
@@ -95,8 +95,8 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager {
// Setup optional cache layer if configured
it.cacheManager = config.options?.cache?.let {
GuavaCacheManager(
- timeToLiveSeconds = it.expiryTimeInSecs,
- maxSize = it.capacity)
+ timeToLiveSeconds = it.expireAfterSecs,
+ maxSize = it.maxEntries)
}
}
}
@@ -149,22 +149,29 @@ private object RPCPermissionResolver : PermissionResolver {
private val ACTION_START_FLOW = "startflow"
private val ACTION_INVOKE_RPC = "invokerpc"
private val ACTION_ALL = "all"
-
- private val FLOW_RPC_CALLS = setOf("startFlowDynamic", "startTrackedFlowDynamic")
+ private val FLOW_RPC_CALLS = setOf(
+ "startFlowDynamic",
+ "startTrackedFlowDynamic",
+ "startFlow",
+ "startTrackedFlow")
override fun resolvePermission(representation: String): Permission {
-
- val action = representation.substringBefore(SEPARATOR).toLowerCase()
+ val action = representation.substringBefore(SEPARATOR).toLowerCase()
when (action) {
ACTION_INVOKE_RPC -> {
- val rpcCall = representation.substringAfter(SEPARATOR)
- require(representation.count { it == SEPARATOR } == 1) {
+ val rpcCall = representation.substringAfter(SEPARATOR, "")
+ require(representation.count { it == SEPARATOR } == 1 && !rpcCall.isEmpty()) {
"Malformed permission string"
}
- return RPCPermission(setOf(rpcCall))
+ val permitted = when(rpcCall) {
+ "startFlow" -> setOf("startFlowDynamic", rpcCall)
+ "startTrackedFlow" -> setOf("startTrackedFlowDynamic", rpcCall)
+ else -> setOf(rpcCall)
+ }
+ return RPCPermission(permitted)
}
ACTION_START_FLOW -> {
- val targetFlow = representation.substringAfter(SEPARATOR)
+ val targetFlow = representation.substringAfter(SEPARATOR, "")
require(targetFlow.isNotEmpty()) {
"Missing target flow after StartFlow"
}
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 c08b81efce..705f4e4fc0 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
@@ -6,10 +6,10 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import net.corda.node.services.messaging.CertificateChainCheckPolicy
-import net.corda.nodeapi.internal.persistence.DatabaseConfig
-import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
+import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.parseAs
+import net.corda.nodeapi.internal.persistence.DatabaseConfig
import java.net.URL
import java.nio.file.Path
import java.util.*
@@ -44,6 +44,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
val sshd: SSHDConfiguration?
val database: DatabaseConfig
val relay: RelayConfiguration?
+ val useAMQPBridges: Boolean get() = true
}
data class DevModeOptions(val disableCheckpointChecker: Boolean = false)
@@ -120,7 +121,8 @@ data class NodeConfigurationImpl(
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(),
override val sshd: SSHDConfiguration? = null,
- override val database: DatabaseConfig = DatabaseConfig(exportHibernateJMXStatistics = devMode)
+ override val database: DatabaseConfig = DatabaseConfig(exportHibernateJMXStatistics = devMode),
+ override val useAMQPBridges: Boolean = true
) : NodeConfiguration {
override val exportJMXto: String get() = "http"
@@ -200,7 +202,7 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ
data class Options(val cache: Options.Cache?) {
// Cache parameters
- data class Cache(val expiryTimeInSecs: Long, val capacity: Long)
+ data class Cache(val expireAfterSecs: Long, val maxEntries: Long)
}
diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt
index be682a7c05..719eea4b75 100644
--- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt
@@ -24,7 +24,7 @@ import javax.annotation.concurrent.ThreadSafe
* @param identities initial set of identities for the service, typically only used for unit tests.
*/
@ThreadSafe
-class InMemoryIdentityService(identities: Iterable,
+class InMemoryIdentityService(identities: Array,
trustRoot: X509CertificateHolder) : SingletonSerializeAsToken(), IdentityServiceInternal {
companion object {
private val log = contextLogger()
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt b/node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt
new file mode 100644
index 0000000000..8b285bf999
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt
@@ -0,0 +1,203 @@
+package net.corda.node.services.messaging
+
+import io.netty.channel.EventLoopGroup
+import io.netty.channel.nio.NioEventLoopGroup
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.node.NodeInfo
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.debug
+import net.corda.node.internal.protonwrapper.messages.MessageStatus
+import net.corda.node.internal.protonwrapper.netty.AMQPClient
+import net.corda.node.services.config.NodeConfiguration
+import net.corda.node.services.messaging.AMQPBridgeManager.AMQPBridge.Companion.getBridgeName
+import net.corda.nodeapi.internal.ArtemisMessagingComponent
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
+import net.corda.nodeapi.internal.crypto.loadKeyStore
+import org.apache.activemq.artemis.api.core.SimpleString
+import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
+import org.apache.activemq.artemis.api.core.client.ClientConsumer
+import org.apache.activemq.artemis.api.core.client.ClientMessage
+import org.apache.activemq.artemis.api.core.client.ClientSession
+import org.slf4j.LoggerFactory
+import rx.Subscription
+import java.security.KeyStore
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/**
+ * The AMQPBridgeManager holds the list of independent AMQPBridge objects that actively ferry messages to remote Artemis
+ * inboxes.
+ * The AMQPBridgeManager also provides a single shared connection to Artemis, although each bridge then creates an
+ * independent Session for message consumption.
+ * The Netty thread pool used by the AMQPBridges is also shared and managed by the AMQPBridgeManager.
+ */
+internal class AMQPBridgeManager(val config: NodeConfiguration, val p2pAddress: NetworkHostAndPort, val maxMessageSize: Int) : BridgeManager {
+
+ private val lock = ReentrantLock()
+ private val bridgeNameToBridgeMap = mutableMapOf()
+ private var sharedEventLoopGroup: EventLoopGroup? = null
+ private val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
+ private val keyStorePrivateKeyPassword: String = config.keyStorePassword
+ private val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
+ private var artemis: ArtemisMessagingClient? = null
+
+ companion object {
+ private const val NUM_BRIDGE_THREADS = 0 // Default sized pool
+ }
+
+ /**
+ * Each AMQPBridge is an independent consumer of messages from the Artemis local queue per designated endpoint.
+ * It attempts to deliver these messages via an AMQPClient instance to the remote Artemis inbox.
+ * To prevent race conditions the Artemis session/consumer is only created when the AMQPClient has a stable AMQP connection.
+ * The acknowledgement and removal of messages from the local queue only occurs if there successful end-to-end delivery.
+ * If the delivery fails the session is rolled back to prevent loss of the message. This may cause duplicate delivery,
+ * however Artemis and the remote Corda instanced will deduplicate these messages.
+ */
+ private class AMQPBridge(private val queueName: String,
+ private val target: NetworkHostAndPort,
+ private val legalNames: Set,
+ keyStore: KeyStore,
+ keyStorePrivateKeyPassword: String,
+ trustStore: KeyStore,
+ sharedEventGroup: EventLoopGroup,
+ private val artemis: ArtemisMessagingClient) {
+ companion object {
+ fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
+ }
+
+ private val log = LoggerFactory.getLogger("$bridgeName:${legalNames.first()}")
+
+ val amqpClient = AMQPClient(listOf(target), legalNames, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, sharedThreadPool = sharedEventGroup)
+ val bridgeName: String get() = getBridgeName(queueName, target)
+ private val lock = ReentrantLock() // lock to serialise session level access
+ private var session: ClientSession? = null
+ private var consumer: ClientConsumer? = null
+ private var connectedSubscription: Subscription? = null
+
+ fun start() {
+ log.info("Create new AMQP bridge")
+ connectedSubscription = amqpClient.onConnection.subscribe({ x -> onSocketConnected(x.connected) })
+ amqpClient.start()
+ }
+
+ fun stop() {
+ log.info("Stopping AMQP bridge")
+ lock.withLock {
+ synchronized(artemis) {
+ consumer?.close()
+ consumer = null
+ session?.stop()
+ session = null
+ }
+ }
+ amqpClient.stop()
+ connectedSubscription?.unsubscribe()
+ connectedSubscription = null
+ }
+
+ private fun onSocketConnected(connected: Boolean) {
+ lock.withLock {
+ synchronized(artemis) {
+ if (connected) {
+ log.info("Bridge Connected")
+ val sessionFactory = artemis.started!!.sessionFactory
+ val session = sessionFactory.createSession(NODE_USER, NODE_USER, false, false, false, false, DEFAULT_ACK_BATCH_SIZE)
+ this.session = session
+ val consumer = session.createConsumer(queueName)
+ this.consumer = consumer
+ consumer.setMessageHandler(this@AMQPBridge::clientArtemisMessageHandler)
+ session.start()
+ } else {
+ log.info("Bridge Disconnected")
+ consumer?.close()
+ consumer = null
+ session?.stop()
+ session = null
+ }
+ }
+ }
+ }
+
+ private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) {
+ lock.withLock {
+ val data = ByteArray(artemisMessage.bodySize).apply { artemisMessage.bodyBuffer.readBytes(this) }
+ val properties = HashMap()
+ for (key in artemisMessage.propertyNames) {
+ var value = artemisMessage.getObjectProperty(key)
+ if (value is SimpleString) {
+ value = value.toString()
+ }
+ properties[key.toString()] = value
+ }
+ log.debug { "Bridged Send to ${legalNames.first()} uuid: ${artemisMessage.getObjectProperty("_AMQ_DUPL_ID")}" }
+ val sendableMessage = amqpClient.createMessage(data, P2P_QUEUE,
+ legalNames.first().toString(),
+ properties)
+ sendableMessage.onComplete.then {
+ log.debug { "Bridge ACK ${sendableMessage.onComplete.get()}" }
+ lock.withLock {
+ if (sendableMessage.onComplete.get() == MessageStatus.Acknowledged) {
+ artemisMessage.acknowledge()
+ session?.commit()
+ } else {
+ log.info("Rollback rejected message uuid: ${artemisMessage.getObjectProperty("_AMQ_DUPL_ID")}")
+ session?.rollback(false)
+ }
+ }
+ }
+ amqpClient.write(sendableMessage)
+ }
+ }
+ }
+
+ private fun gatherAddresses(node: NodeInfo): Sequence {
+ val address = node.addresses.first()
+ return node.legalIdentitiesAndCerts.map { ArtemisMessagingComponent.NodeAddress(it.party.owningKey, address) }.asSequence()
+ }
+
+ override fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set) {
+ if (bridgeExists(getBridgeName(queueName, target))) {
+ return
+ }
+ val newBridge = AMQPBridge(queueName, target, legalNames, keyStore, keyStorePrivateKeyPassword, trustStore, sharedEventLoopGroup!!, artemis!!)
+ lock.withLock {
+ bridgeNameToBridgeMap[newBridge.bridgeName] = newBridge
+ }
+ newBridge.start()
+ }
+
+ override fun destroyBridges(node: NodeInfo) {
+ lock.withLock {
+ gatherAddresses(node).forEach {
+ val bridge = bridgeNameToBridgeMap.remove(getBridgeName(it.queueName, it.hostAndPort))
+ bridge?.stop()
+ }
+ }
+ }
+
+ override fun bridgeExists(bridgeName: String): Boolean = lock.withLock { bridgeNameToBridgeMap.containsKey(bridgeName) }
+
+ override fun start() {
+ sharedEventLoopGroup = NioEventLoopGroup(NUM_BRIDGE_THREADS)
+ val artemis = ArtemisMessagingClient(config, p2pAddress, maxMessageSize)
+ this.artemis = artemis
+ artemis.start()
+ }
+
+ override fun stop() = close()
+
+ override fun close() {
+ lock.withLock {
+ for (bridge in bridgeNameToBridgeMap.values) {
+ bridge.stop()
+ }
+ sharedEventLoopGroup?.shutdownGracefully()
+ sharedEventLoopGroup?.terminationFuture()?.sync()
+ sharedEventLoopGroup = null
+ bridgeNameToBridgeMap.clear()
+ artemis?.stop()
+ }
+ }
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt
index fd36dc9ba7..84dd18298d 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt
@@ -3,14 +3,17 @@ package net.corda.node.services.messaging
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
-import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.ConnectionDirection
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.internal.config.SSLConfiguration
-import org.apache.activemq.artemis.api.core.client.*
+import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
+import org.apache.activemq.artemis.api.core.client.ClientProducer
+import org.apache.activemq.artemis.api.core.client.ClientSession
+import org.apache.activemq.artemis.api.core.client.ClientSessionFactory
-class ArtemisMessagingClient(private val config: SSLConfiguration, private val serverAddress: NetworkHostAndPort) {
+class ArtemisMessagingClient(private val config: SSLConfiguration, private val serverAddress: NetworkHostAndPort, private val maxMessageSize: Int) {
companion object {
private val log = loggerFor()
}
@@ -30,7 +33,7 @@ class ArtemisMessagingClient(private val config: SSLConfiguration, private val s
// would be the default and the two lines below can be deleted.
connectionTTL = -1
clientFailureCheckPeriod = -1
- minLargeMessageSize = ArtemisMessagingServer.MAX_FILE_SIZE
+ minLargeMessageSize = maxMessageSize
isUseGlobalPools = nodeSerializationEnv != null
}
val sessionFactory = locator.createSessionFactory()
@@ -46,7 +49,7 @@ class ArtemisMessagingClient(private val config: SSLConfiguration, private val s
}
fun stop() = synchronized(this) {
- started!!.run {
+ started?.run {
producer.close()
// Ensure any trailing messages are committed to the journal
session.commit()
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 5634ca74ed..285d4ce94c 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,6 +1,5 @@
package net.corda.node.services.messaging
-import io.netty.handler.ssl.SslHandler
import net.corda.core.crypto.AddressFormatException
import net.corda.core.crypto.newSecureRandom
import net.corda.core.identity.CordaX500Name
@@ -24,11 +23,10 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE
-import net.corda.nodeapi.internal.crypto.X509Utilities
-import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
-import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
-import net.corda.nodeapi.internal.crypto.loadKeyStore
-import net.corda.nodeapi.*
+import net.corda.nodeapi.ArtemisTcpTransport
+import net.corda.nodeapi.ConnectionDirection
+import net.corda.nodeapi.RPCApi
+import net.corda.nodeapi.VerifierApi
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisPeerAddress
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
@@ -37,15 +35,17 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
+import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
+import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
+import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.requireOnDefaultFileSystem
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
-import org.apache.activemq.artemis.core.config.BridgeConfiguration
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.remoting.impl.netty.*
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
import org.apache.activemq.artemis.core.security.Role
import org.apache.activemq.artemis.core.server.ActiveMQServer
import org.apache.activemq.artemis.core.server.SecuritySettingPlugin
@@ -53,22 +53,17 @@ import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
import org.apache.activemq.artemis.core.settings.HierarchicalRepository
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.*
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateCallback
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
-import org.apache.activemq.artemis.utils.ConfigurationHelper
import rx.Subscription
import java.io.IOException
import java.math.BigInteger
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.Principal
-import java.time.Duration
import java.util.*
-import java.util.concurrent.Executor
-import java.util.concurrent.ScheduledExecutorService
import javax.annotation.concurrent.ThreadSafe
import javax.security.auth.Subject
import javax.security.auth.callback.CallbackHandler
@@ -80,7 +75,6 @@ import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.RE
import javax.security.auth.login.FailedLoginException
import javax.security.auth.login.LoginException
import javax.security.auth.spi.LoginModule
-import javax.security.auth.x500.X500Principal
import javax.security.cert.CertificateException
// TODO: Verify that nobody can connect to us and fiddle with our config over the socket due to the secman.
@@ -101,14 +95,11 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
private val p2pPort: Int,
val rpcPort: Int?,
val networkMapCache: NetworkMapCache,
- val securityManager: RPCSecurityManager) : SingletonSerializeAsToken() {
+ val securityManager: RPCSecurityManager,
+ val maxMessageSize: Int) : SingletonSerializeAsToken() {
companion object {
private val log = contextLogger()
- /** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */
- @JvmStatic
- val MAX_FILE_SIZE = 10485760
}
-
private class InnerState {
var running = false
}
@@ -117,6 +108,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
private lateinit var activeMQServer: ActiveMQServer
val serverControl: ActiveMQServerControl get() = activeMQServer.activeMQServerControl
private var networkChangeHandle: Subscription? = null
+ private lateinit var bridgeManager: BridgeManager
init {
config.baseDirectory.requireOnDefaultFileSystem()
@@ -136,6 +128,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
}
fun stop() = mutex.locked {
+ bridgeManager.close()
networkChangeHandle?.unsubscribe()
networkChangeHandle = null
activeMQServer.stop()
@@ -156,7 +149,14 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
registerPostQueueCreationCallback { deployBridgesFromNewQueue(it.toString()) }
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
}
+ // Config driven switch between legacy CORE bridges and the newer AMQP protocol bridges.
+ bridgeManager = if (config.useAMQPBridges) {
+ AMQPBridgeManager(config, NetworkHostAndPort("localhost", p2pPort), maxMessageSize)
+ } else {
+ CoreBridgeManager(config, activeMQServer)
+ }
activeMQServer.start()
+ bridgeManager.start()
Node.printBasicNodeInfo("Listening on port", p2pPort.toString())
if (rpcPort != null) {
Node.printBasicNodeInfo("RPC service listening on port", rpcPort.toString())
@@ -181,9 +181,9 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
isPersistIDCache = true
isPopulateValidatedUser = true
- journalBufferSize_NIO = MAX_FILE_SIZE // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
- journalBufferSize_AIO = MAX_FILE_SIZE // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store.
- journalFileSize = MAX_FILE_SIZE // The size of each journal file in bytes. Artemis default is 10MiB.
+ journalBufferSize_NIO = maxMessageSize // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
+ journalBufferSize_AIO = maxMessageSize // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store.
+ journalFileSize = maxMessageSize // The size of each journal file in bytes. Artemis default is 10MiB.
managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS)
// Artemis allows multiple servers to be grouped together into a cluster for load balancing purposes. The cluster
// user is used for connecting the nodes together. It has super-user privileges and so it's imperative that its
@@ -211,15 +211,17 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
)
addressesSettings = mapOf(
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.#" to AddressSettings().apply {
- maxSizeBytes = 10L * MAX_FILE_SIZE
+ maxSizeBytes = 10L * maxMessageSize
addressFullMessagePolicy = AddressFullMessagePolicy.FAIL
}
)
- // JMX enablement
- if (config.exportJMXto.isNotEmpty()) {isJMXManagementEnabled = true
- isJMXUseBrokerName = true}
+ // JMX enablement
+ if (config.exportJMXto.isNotEmpty()) {
+ isJMXManagementEnabled = true
+ isJMXUseBrokerName = true
+ }
- }.configureAddressSecurity()
+ }.configureAddressSecurity()
private fun queueConfig(name: String, address: String = name, filter: String? = null, durable: Boolean): CoreQueueConfiguration {
@@ -302,7 +304,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
fun deployBridgeToPeer(nodeInfo: NodeInfo) {
log.debug("Deploying bridge for $queueName to $nodeInfo")
val address = nodeInfo.addresses.first()
- deployBridge(queueName, address, nodeInfo.legalIdentitiesAndCerts.map { it.name }.toSet())
+ bridgeManager.deployBridge(queueName, address, nodeInfo.legalIdentitiesAndCerts.map { it.name }.toSet())
}
if (queueName.startsWith(PEERS_PREFIX)) {
@@ -337,147 +339,39 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
fun deployBridges(node: NodeInfo) {
gatherAddresses(node)
- .filter { queueExists(it.queueName) && !bridgeExists(it.bridgeName) }
+ .filter { queueExists(it.queueName) && !bridgeManager.bridgeExists(it.bridgeName) }
.forEach { deployBridge(it, node.legalIdentitiesAndCerts.map { it.name }.toSet()) }
}
- fun destroyBridges(node: NodeInfo) {
- gatherAddresses(node).forEach {
- activeMQServer.destroyBridge(it.bridgeName)
- }
- }
-
when (change) {
is MapChange.Added -> {
deployBridges(change.node)
}
is MapChange.Removed -> {
- destroyBridges(change.node)
+ bridgeManager.destroyBridges(change.node)
}
is MapChange.Modified -> {
// TODO Figure out what has actually changed and only destroy those bridges that need to be.
- destroyBridges(change.previousNode)
+ bridgeManager.destroyBridges(change.previousNode)
deployBridges(change.node)
}
}
}
private fun deployBridge(address: ArtemisPeerAddress, legalNames: Set) {
- deployBridge(address.queueName, address.hostAndPort, legalNames)
+ bridgeManager.deployBridge(address.queueName, address.hostAndPort, legalNames)
}
private fun createTcpTransport(connectionDirection: ConnectionDirection, host: String, port: Int, enableSSL: Boolean = true) =
ArtemisTcpTransport.tcpTransport(connectionDirection, NetworkHostAndPort(host, port), config, enableSSL = enableSSL)
- /**
- * All nodes are expected to have a public facing address called [ArtemisMessagingComponent.P2P_QUEUE] for receiving
- * messages from other nodes. When we want to send a message to a node we send it to our internal address/queue for it,
- * as defined by ArtemisAddress.queueName. A bridge is then created to forward messages from this queue to the node's
- * P2P address.
- */
- private fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set) {
- val connectionDirection = ConnectionDirection.Outbound(
- connectorFactoryClassName = VerifyingNettyConnectorFactory::class.java.name,
- expectedCommonNames = legalNames
- )
- val tcpTransport = createTcpTransport(connectionDirection, target.host, target.port)
- tcpTransport.params[ArtemisMessagingServer::class.java.name] = this
- // We intentionally overwrite any previous connector config in case the peer legal name changed
- activeMQServer.configuration.addConnectorConfiguration(target.toString(), tcpTransport)
-
- activeMQServer.deployBridge(BridgeConfiguration().apply {
- name = getBridgeName(queueName, target)
- this.queueName = queueName
- forwardingAddress = P2P_QUEUE
- staticConnectors = listOf(target.toString())
- confirmationWindowSize = 100000 // a guess
- isUseDuplicateDetection = true // Enable the bridge's automatic deduplication logic
- // We keep trying until the network map deems the node unreachable and tells us it's been removed at which
- // point we destroy the bridge
- retryInterval = config.activeMQServer.bridge.retryIntervalMs
- retryIntervalMultiplier = config.activeMQServer.bridge.retryIntervalMultiplier
- maxRetryInterval = Duration.ofMinutes(config.activeMQServer.bridge.maxRetryIntervalMin).toMillis()
- // As a peer of the target node we must connect to it using the peer user. Actual authentication is done using
- // our TLS certificate.
- user = PEER_USER
- password = PEER_USER
- })
- }
-
private fun queueExists(queueName: String): Boolean = activeMQServer.queueQuery(SimpleString(queueName)).isExists
- private fun bridgeExists(bridgeName: String): Boolean = activeMQServer.clusterManager.bridges.containsKey(bridgeName)
-
private val ArtemisPeerAddress.bridgeName: String get() = getBridgeName(queueName, hostAndPort)
private fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
}
-class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
- override fun createConnector(configuration: MutableMap,
- handler: BufferHandler?,
- listener: ClientConnectionLifeCycleListener?,
- closeExecutor: Executor?,
- threadPool: Executor?,
- scheduledThreadPool: ScheduledExecutorService?,
- protocolManager: ClientProtocolManager?): Connector {
- return VerifyingNettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool,
- protocolManager)
- }
-}
-
-private class VerifyingNettyConnector(configuration: MutableMap,
- handler: BufferHandler?,
- listener: ClientConnectionLifeCycleListener?,
- closeExecutor: Executor?,
- threadPool: Executor?,
- scheduledThreadPool: ScheduledExecutorService?,
- protocolManager: ClientProtocolManager?) :
- NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) {
- companion object {
- private val log = contextLogger()
- }
-
- private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration)
-
- override fun createConnection(): Connection? {
- val connection = super.createConnection() as? NettyConnection
- if (sslEnabled && connection != null) {
- val expectedLegalNames: Set = uncheckedCast(configuration[ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME] ?: emptySet())
- try {
- val session = connection.channel
- .pipeline()
- .get(SslHandler::class.java)
- .engine()
- .session
- // Checks the peer name is the one we are expecting.
- // TODO Some problems here: after introduction of multiple legal identities on the node and removal of the main one,
- // we run into the issue, who are we connecting to. There are some solutions to that: advertise `network identity`;
- // have mapping port -> identity (but, design doc says about removing SingleMessageRecipient and having just NetworkHostAndPort,
- // it was convenient to store that this way); SNI.
- val peerLegalName = CordaX500Name.parse(session.peerPrincipal.name)
- val expectedLegalName = expectedLegalNames.singleOrNull { it == peerLegalName }
- require(expectedLegalName != null) {
- "Peer has wrong CN - expected $expectedLegalNames but got $peerLegalName. This is either a fatal " +
- "misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
- }
- // Make sure certificate has the same name.
- val peerCertificateName = CordaX500Name.build(X500Principal(session.peerCertificateChain[0].subjectDN.name))
- require(peerCertificateName == expectedLegalName) {
- "Peer has wrong subject name in the certificate - expected $expectedLegalNames but got $peerCertificateName. This is either a fatal " +
- "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)
- } catch (e: IllegalArgumentException) {
- connection.close()
- log.error(e.message)
- return null
- }
- }
- return connection
- }
-}
-
sealed class CertificateChainCheckPolicy {
@FunctionalInterface
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/BridgeManager.kt b/node/src/main/kotlin/net/corda/node/services/messaging/BridgeManager.kt
new file mode 100644
index 0000000000..5997921848
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/BridgeManager.kt
@@ -0,0 +1,20 @@
+package net.corda.node.services.messaging
+
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.node.NodeInfo
+import net.corda.core.utilities.NetworkHostAndPort
+
+/**
+ * Provides an internal interface that the [ArtemisMessagingServer] delegates to for Bridge activities.
+ */
+internal interface BridgeManager : AutoCloseable {
+ fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set)
+
+ fun destroyBridges(node: NodeInfo)
+
+ fun bridgeExists(bridgeName: String): Boolean
+
+ fun start()
+
+ fun stop()
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt b/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt
new file mode 100644
index 0000000000..4161d89836
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt
@@ -0,0 +1,166 @@
+package net.corda.node.services.messaging
+
+import io.netty.handler.ssl.SslHandler
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.internal.uncheckedCast
+import net.corda.core.node.NodeInfo
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.contextLogger
+import net.corda.node.services.config.NodeConfiguration
+import net.corda.nodeapi.ArtemisTcpTransport
+import net.corda.nodeapi.ConnectionDirection
+import net.corda.nodeapi.internal.ArtemisMessagingComponent
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
+import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
+import net.corda.nodeapi.internal.crypto.X509Utilities
+import org.apache.activemq.artemis.core.config.BridgeConfiguration
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
+import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants
+import org.apache.activemq.artemis.core.server.ActiveMQServer
+import org.apache.activemq.artemis.spi.core.remoting.*
+import org.apache.activemq.artemis.utils.ConfigurationHelper
+import java.time.Duration
+import java.util.concurrent.Executor
+import java.util.concurrent.ScheduledExecutorService
+import javax.security.auth.x500.X500Principal
+
+/**
+ * This class simply moves the legacy CORE bridge code from [ArtemisMessagingServer]
+ * into a class implementing [BridgeManager].
+ * It has no lifecycle events, because the bridges are internal to the ActiveMQServer instance and thus
+ * stop when it is stopped.
+ */
+internal class CoreBridgeManager(val config: NodeConfiguration, val activeMQServer: ActiveMQServer) : BridgeManager {
+ companion object {
+ private fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
+
+ private val ArtemisMessagingComponent.ArtemisPeerAddress.bridgeName: String get() = getBridgeName(queueName, hostAndPort)
+ }
+
+ private fun gatherAddresses(node: NodeInfo): Sequence {
+ val address = node.addresses.first()
+ return node.legalIdentitiesAndCerts.map { ArtemisMessagingComponent.NodeAddress(it.party.owningKey, address) }.asSequence()
+ }
+
+
+ /**
+ * All nodes are expected to have a public facing address called [ArtemisMessagingComponent.P2P_QUEUE] for receiving
+ * messages from other nodes. When we want to send a message to a node we send it to our internal address/queue for it,
+ * as defined by ArtemisAddress.queueName. A bridge is then created to forward messages from this queue to the node's
+ * P2P address.
+ */
+ override fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set) {
+ val connectionDirection = ConnectionDirection.Outbound(
+ connectorFactoryClassName = VerifyingNettyConnectorFactory::class.java.name,
+ expectedCommonNames = legalNames
+ )
+ val tcpTransport = ArtemisTcpTransport.tcpTransport(connectionDirection, target, config, enableSSL = true)
+ tcpTransport.params[ArtemisMessagingServer::class.java.name] = this
+ // We intentionally overwrite any previous connector config in case the peer legal name changed
+ activeMQServer.configuration.addConnectorConfiguration(target.toString(), tcpTransport)
+
+ activeMQServer.deployBridge(BridgeConfiguration().apply {
+ name = getBridgeName(queueName, target)
+ this.queueName = queueName
+ forwardingAddress = P2P_QUEUE
+ staticConnectors = listOf(target.toString())
+ confirmationWindowSize = 100000 // a guess
+ isUseDuplicateDetection = true // Enable the bridge's automatic deduplication logic
+ // We keep trying until the network map deems the node unreachable and tells us it's been removed at which
+ // point we destroy the bridge
+ retryInterval = config.activeMQServer.bridge.retryIntervalMs
+ retryIntervalMultiplier = config.activeMQServer.bridge.retryIntervalMultiplier
+ maxRetryInterval = Duration.ofMinutes(config.activeMQServer.bridge.maxRetryIntervalMin).toMillis()
+ // As a peer of the target node we must connect to it using the peer user. Actual authentication is done using
+ // our TLS certificate.
+ user = PEER_USER
+ password = PEER_USER
+ })
+ }
+
+ override fun bridgeExists(bridgeName: String): Boolean = activeMQServer.clusterManager.bridges.containsKey(bridgeName)
+
+ override fun start() {
+ // Nothing to do
+ }
+
+ override fun stop() {
+ // Nothing to do
+ }
+
+ override fun close() = stop()
+
+ override fun destroyBridges(node: NodeInfo) {
+ gatherAddresses(node).forEach {
+ activeMQServer.destroyBridge(it.bridgeName)
+ }
+ }
+}
+
+class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
+ override fun createConnector(configuration: MutableMap,
+ handler: BufferHandler?,
+ listener: ClientConnectionLifeCycleListener?,
+ closeExecutor: Executor?,
+ threadPool: Executor?,
+ scheduledThreadPool: ScheduledExecutorService?,
+ protocolManager: ClientProtocolManager?): Connector {
+ return VerifyingNettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool,
+ protocolManager)
+ }
+
+ private class VerifyingNettyConnector(configuration: MutableMap,
+ handler: BufferHandler?,
+ listener: ClientConnectionLifeCycleListener?,
+ closeExecutor: Executor?,
+ threadPool: Executor?,
+ scheduledThreadPool: ScheduledExecutorService?,
+ protocolManager: ClientProtocolManager?) :
+ NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) {
+ companion object {
+ private val log = contextLogger()
+ }
+
+ private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration)
+
+ override fun createConnection(): Connection? {
+ val connection = super.createConnection() as? NettyConnection
+ if (sslEnabled && connection != null) {
+ val expectedLegalNames: Set = uncheckedCast(configuration[ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME] ?: emptySet())
+ try {
+ val session = connection.channel
+ .pipeline()
+ .get(SslHandler::class.java)
+ .engine()
+ .session
+ // Checks the peer name is the one we are expecting.
+ // TODO Some problems here: after introduction of multiple legal identities on the node and removal of the main one,
+ // we run into the issue, who are we connecting to. There are some solutions to that: advertise `network identity`;
+ // have mapping port -> identity (but, design doc says about removing SingleMessageRecipient and having just NetworkHostAndPort,
+ // it was convenient to store that this way); SNI.
+ val peerLegalName = CordaX500Name.parse(session.peerPrincipal.name)
+ val expectedLegalName = expectedLegalNames.singleOrNull { it == peerLegalName }
+ require(expectedLegalName != null) {
+ "Peer has wrong CN - expected $expectedLegalNames but got $peerLegalName. This is either a fatal " +
+ "misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
+ }
+ // Make sure certificate has the same name.
+ val peerCertificateName = CordaX500Name.build(X500Principal(session.peerCertificateChain[0].subjectDN.name))
+ require(peerCertificateName == expectedLegalName) {
+ "Peer has wrong subject name in the certificate - expected $expectedLegalNames but got $peerCertificateName. This is either a fatal " +
+ "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)
+ } catch (e: IllegalArgumentException) {
+ connection.close()
+ log.error(e.message)
+ return null
+ }
+ }
+ return connection
+ }
+ }
+}
+
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt
index 753e9ff330..0e305c6f4c 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt
@@ -67,7 +67,8 @@ class P2PMessagingClient(config: NodeConfiguration,
private val myIdentity: PublicKey,
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
private val database: CordaPersistence,
- advertisedAddress: NetworkHostAndPort = serverAddress
+ advertisedAddress: NetworkHostAndPort = serverAddress,
+ private val maxMessageSize: Int
) : SingletonSerializeAsToken(), MessagingService {
companion object {
private val log = contextLogger()
@@ -143,7 +144,7 @@ class P2PMessagingClient(config: NodeConfiguration,
override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress)
private val messageRedeliveryDelaySeconds = config.messageRedeliveryDelaySeconds.toLong()
- private val artemis = ArtemisMessagingClient(config, serverAddress)
+ private val artemis = ArtemisMessagingClient(config, serverAddress, maxMessageSize)
private val state = ThreadBox(InnerState())
private val handlers = ConcurrentHashMap()
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt
index 3d1ee3e3d6..e9fe065ac6 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt
@@ -12,8 +12,8 @@ import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.crypto.loadKeyStore
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
-class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort) : SingletonSerializeAsToken() {
- private val artemis = ArtemisMessagingClient(config, serverAddress)
+class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort, private val maxMessageSize: Int) : SingletonSerializeAsToken() {
+ private val artemis = ArtemisMessagingClient(config, serverAddress, maxMessageSize)
private var rpcServer: RPCServer? = null
fun start(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) {
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt
index 8216b6846b..9a8c0cae66 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt
@@ -17,13 +17,13 @@ import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.*
import java.util.concurrent.*
-class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHostAndPort, metrics: MetricRegistry) : SingletonSerializeAsToken() {
+class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHostAndPort, metrics: MetricRegistry, private val maxMessageSize: Int) : SingletonSerializeAsToken() {
companion object {
private val log = loggerFor()
private val verifierResponseAddress = "$VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX.${random63BitValue()}"
}
- private val artemis = ArtemisMessagingClient(config, serverAddress)
+ private val artemis = ArtemisMessagingClient(config, serverAddress, maxMessageSize)
/** An executor for sending messages */
private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1)
private var verificationResponseConsumer: ClientConsumer? = null
diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt
index 66fe9470bd..db7bea9480 100644
--- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt
+++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt
@@ -12,10 +12,10 @@ import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.utilities.NamedThreadFactory
-import net.corda.nodeapi.internal.NetworkMap
-import net.corda.nodeapi.internal.NetworkParameters
-import net.corda.nodeapi.internal.SignedNetworkMap
-import net.corda.nodeapi.internal.crypto.X509Utilities
+import net.corda.nodeapi.internal.network.NetworkMap
+import net.corda.nodeapi.internal.network.NetworkParameters
+import net.corda.nodeapi.internal.network.SignedNetworkMap
+import net.corda.nodeapi.internal.SignedNodeInfo
import okhttp3.CacheControl
import okhttp3.Headers
import rx.Subscription
@@ -31,13 +31,13 @@ import java.util.concurrent.TimeUnit
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
- fun publish(signedNodeInfo: SignedData) {
+ fun publish(signedNodeInfo: SignedNodeInfo) {
val publishURL = URL("$networkMapUrl/publish")
val conn = publishURL.openHttpConnection()
conn.doOutput = true
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/octet-stream")
- conn.outputStream.use { it.write(signedNodeInfo.serialize().bytes) }
+ conn.outputStream.use { signedNodeInfo.serialize().open().copyTo(it) }
// This will throw IOException if the response code is not HTTP 200.
// This gives a much better exception then reading the error stream.
@@ -47,9 +47,7 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C
fun getNetworkMap(): NetworkMapResponse {
val conn = networkMapUrl.openHttpConnection()
val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize()
- val networkMap = signedNetworkMap.verified()
- // Assume network map cert is issued by the root.
- X509Utilities.validateCertificateChain(trustedRoot, signedNetworkMap.sig.by, trustedRoot)
+ val networkMap = signedNetworkMap.verified(trustedRoot)
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
return NetworkMapResponse(networkMap, timeout)
}
@@ -59,16 +57,12 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
null
} else {
- val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize>()
- val nodeInfo = signedNodeInfo.verified()
- // Verify node info is signed by node identity
- // TODO : Validate multiple signatures when NodeInfo supports multiple identities.
- require(nodeInfo.legalIdentities.any { it.owningKey == signedNodeInfo.sig.by }) { "NodeInfo must be signed by the node owning key." }
- nodeInfo
+ val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize()
+ signedNodeInfo.verified()
}
}
- fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameters? {
+ fun getNetworkParameter(networkParameterHash: SecureHash): SignedData? {
val conn = URL("$networkMapUrl/network-parameter/$networkParameterHash").openHttpConnection()
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
null
@@ -87,7 +81,8 @@ data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Durat
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val fileWatcher: NodeInfoWatcher,
- private val networkMapClient: NetworkMapClient?) : Closeable {
+ private val networkMapClient: NetworkMapClient?,
+ private val currentParametersHash: SecureHash) : Closeable {
companion object {
private val logger = contextLogger()
private val retryInterval = 1.minutes
@@ -101,7 +96,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
MoreExecutors.shutdownAndAwaitTermination(executor, 50, TimeUnit.SECONDS)
}
- fun updateNodeInfo(newInfo: NodeInfo, signNodeInfo: (NodeInfo) -> SignedData) {
+ fun updateNodeInfo(newInfo: NodeInfo, signNodeInfo: (NodeInfo) -> SignedNodeInfo) {
val oldInfo = networkMapCache.getNodeByLegalIdentity(newInfo.legalIdentities.first())
// Compare node info without timestamp.
if (newInfo.copy(serial = 0L) == oldInfo?.copy(serial = 0L)) return
@@ -127,15 +122,21 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
override fun run() {
val nextScheduleDelay = try {
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
+ // TODO NetworkParameters updates are not implemented yet. Every mismatch should result in node shutdown.
+ if (currentParametersHash != networkMap.networkParameterHash) {
+ logger.error("Node is using parameters with hash: $currentParametersHash but network map is advertising: ${networkMap.networkParameterHash}.\n" +
+ "Please update node to use correct network parameters file.\"")
+ System.exit(1)
+ }
val currentNodeHashes = networkMapCache.allNodeHashes
val hashesFromNetworkMap = networkMap.nodeInfoHashes
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
// Download new node info from network map
try {
networkMapClient.getNodeInfo(it)
- } catch (t: Throwable) {
+ } catch (e: Exception) {
// Failure to retrieve one node info shouldn't stop the whole update, log and return null instead.
- logger.warn("Error encountered when downloading node info '$it', skipping...", t)
+ logger.warn("Error encountered when downloading node info '$it', skipping...", e)
null
}
}.forEach {
@@ -146,7 +147,6 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
.mapNotNull(networkMapCache::getNodeByHash)
.forEach(networkMapCache::removeNode)
- // TODO: Check NetworkParameter.
cacheTimeout
} catch (t: Throwable) {
logger.warn("Error encountered while updating network map, will retry in ${retryInterval.seconds} seconds", t)
@@ -159,7 +159,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
executor.submit(task) // The check may be expensive, so always run it in the background even the first time.
}
- private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedData, networkMapClient: NetworkMapClient) {
+ private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
val task = object : Runnable {
override fun run() {
try {
diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt
index 771604ba6b..3031349d84 100644
--- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt
+++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt
@@ -2,14 +2,14 @@ package net.corda.node.services.network
import net.corda.cordform.CordformNode
import net.corda.core.crypto.SecureHash
-import net.corda.core.crypto.SignedData
import net.corda.core.internal.*
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.seconds
-import net.corda.nodeapi.internal.NodeInfoFilesCopier
+import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
+import net.corda.nodeapi.internal.SignedNodeInfo
import rx.Observable
import rx.Scheduler
import java.io.IOException
@@ -41,14 +41,14 @@ class NodeInfoWatcher(private val nodePath: Path,
private val logger = contextLogger()
/**
* Saves the given [NodeInfo] to a path.
- * The node is 'encoded' as a SignedData, signed with the owning key of its first identity.
+ * The node is 'encoded' as a SignedNodeInfo, signed with the owning key of its first identity.
* The name of the written file will be "nodeInfo-" followed by the hash of the content. The hash in the filename
* is used so that one can freely copy these files without fearing to overwrite another one.
*
* @param path the path where to write the file, if non-existent it will be created.
* @param signedNodeInfo the signed NodeInfo.
*/
- fun saveToFile(path: Path, signedNodeInfo: SignedData) {
+ fun saveToFile(path: Path, signedNodeInfo: SignedNodeInfo) {
try {
path.createDirectories()
signedNodeInfo.serialize()
@@ -85,7 +85,7 @@ class NodeInfoWatcher(private val nodePath: Path,
.flatMapIterable { loadFromDirectory() }
}
- fun saveToFile(signedNodeInfo: SignedData) = Companion.saveToFile(nodePath, signedNodeInfo)
+ fun saveToFile(signedNodeInfo: SignedNodeInfo) = Companion.saveToFile(nodePath, signedNodeInfo)
/**
* Loads all the files contained in a given path and returns the deserialized [NodeInfo]s.
@@ -118,7 +118,7 @@ class NodeInfoWatcher(private val nodePath: Path,
private fun processFile(file: Path): NodeInfo? {
return try {
logger.info("Reading NodeInfo from file: $file")
- val signedData = file.readAll().deserialize>()
+ val signedData = file.readAll().deserialize()
signedData.verified()
} catch (e: Exception) {
logger.warn("Exception parsing NodeInfo from file. $file", e)
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 be52529785..13881de63c 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
@@ -25,7 +25,7 @@ import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
-import net.corda.nodeapi.internal.NotaryInfo
+import net.corda.nodeapi.internal.network.NotaryInfo
import org.hibernate.Session
import rx.Observable
import rx.subjects.PublishSubject
@@ -208,9 +208,6 @@ open class PersistentNetworkMapCache(
getAllInfos(session).map { it.toNodeInfo() }
}
- // Changes related to NetworkMap redesign
- // TODO It will be properly merged into network map cache after services removal.
-
private fun getAllInfos(session: Session): List {
val criteria = session.criteriaBuilder.createQuery(NodeInfoSchemaV1.PersistentNodeInfo::class.java)
criteria.select(criteria.from(NodeInfoSchemaV1.PersistentNodeInfo::class.java))
@@ -293,7 +290,6 @@ open class PersistentNetworkMapCache(
else result.map { it.toNodeInfo() }.singleOrNull() ?: throw IllegalStateException("More than one node with the same host and port")
}
-
/** Object Relational Mapping support. */
private fun generateMappedObject(nodeInfo: NodeInfo): NodeInfoSchemaV1.PersistentNodeInfo {
return NodeInfoSchemaV1.PersistentNodeInfo(
diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf
index 6205b0d073..9caf275e2f 100644
--- a/node/src/main/resources/reference.conf
+++ b/node/src/main/resources/reference.conf
@@ -33,3 +33,5 @@ enterpriseConfiguration = {
waitInterval = 40000
}
}
+
+useAMQPBridges = true
\ No newline at end of file
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 bee1dba5ee..7a5b41fcc1 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
@@ -24,10 +24,9 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence;
import net.corda.nodeapi.internal.persistence.DatabaseTransaction;
import net.corda.testing.SerializationEnvironmentRule;
import net.corda.testing.TestIdentity;
-import net.corda.testing.contracts.DummyLinearContract;
-import net.corda.testing.contracts.VaultFiller;
+import net.corda.testing.internal.vault.DummyLinearContract;
+import net.corda.testing.internal.vault.VaultFiller;
import net.corda.testing.node.MockServices;
-import net.corda.testing.schemas.DummyLinearStateSchemaV1;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -46,20 +45,20 @@ import java.util.stream.StreamSupport;
import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM;
import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE;
import static net.corda.core.utilities.ByteArrays.toHexString;
-import static net.corda.testing.CoreTestUtils.rigorousMock;
-import static net.corda.testing.TestConstants.getBOC_NAME;
-import static net.corda.testing.TestConstants.getCHARLIE_NAME;
-import static net.corda.testing.TestConstants.getDUMMY_NOTARY_NAME;
+import static net.corda.testing.internal.InternalTestUtilsKt.rigorousMock;
+import static net.corda.testing.TestConstants.BOC_NAME;
+import static net.corda.testing.TestConstants.CHARLIE_NAME;
+import static net.corda.testing.TestConstants.DUMMY_NOTARY_NAME;
import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices;
import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
import static org.assertj.core.api.Assertions.assertThat;
public class VaultQueryJavaTests {
- private static final TestIdentity BOC = new TestIdentity(getBOC_NAME());
- private static final Party CHARLIE = new TestIdentity(getCHARLIE_NAME(), 90L).getParty();
+ private static final TestIdentity BOC = new TestIdentity(BOC_NAME);
+ private static final Party CHARLIE = new TestIdentity(CHARLIE_NAME, 90L).getParty();
private static final TestIdentity DUMMY_CASH_ISSUER_INFO = new TestIdentity(new CordaX500Name("Snake Oil Issuer", "London", "GB"), 10L);
private static final PartyAndReference DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_INFO.ref((byte) 1);
- private static final TestIdentity DUMMY_NOTARY = new TestIdentity(getDUMMY_NOTARY_NAME(), 20L);
+ private static final TestIdentity DUMMY_NOTARY = new TestIdentity(DUMMY_NOTARY_NAME, 20L);
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
@Rule
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
@@ -71,17 +70,17 @@ public class VaultQueryJavaTests {
@Before
public void setUp() throws CertificateException, InvalidAlgorithmParameterException {
List cordappPackages = Arrays.asList(
- "net.corda.testing.contracts",
+ "net.corda.testing.internal.vault",
"net.corda.finance.contracts.asset",
CashSchemaV1.class.getPackage().getName(),
DummyLinearStateSchemaV1.class.getPackage().getName());
- IdentityServiceInternal identitySvc = makeTestIdentityService(Arrays.asList(MEGA_CORP.getIdentity(), DUMMY_CASH_ISSUER_INFO.getIdentity(), DUMMY_NOTARY.getIdentity()));
+ IdentityServiceInternal identitySvc = makeTestIdentityService(MEGA_CORP.getIdentity(), DUMMY_CASH_ISSUER_INFO.getIdentity(), DUMMY_NOTARY.getIdentity());
Pair databaseAndServices = makeTestDatabaseAndMockServices(
- Arrays.asList(MEGA_CORP.getKey(), DUMMY_NOTARY.getKey()),
- identitySvc,
cordappPackages,
- MEGA_CORP.getName());
- issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), DUMMY_CASH_ISSUER_INFO, BOC.getKey());
+ identitySvc,
+ MEGA_CORP,
+ DUMMY_NOTARY.getKeyPair());
+ issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), DUMMY_CASH_ISSUER_INFO, BOC.getKeyPair());
database = databaseAndServices.getFirst();
MockServices services = databaseAndServices.getSecond();
vaultFiller = new VaultFiller(services, DUMMY_NOTARY);
@@ -471,16 +470,16 @@ public class VaultQueryJavaTests {
assertThat(results.getOtherResults()).hasSize(12);
assertThat(results.getOtherResults().get(0)).isEqualTo(400L);
- assertThat(results.getOtherResults().get(1)).isEqualTo(CryptoUtils.toStringShort(BOC.getPubkey()));
+ assertThat(results.getOtherResults().get(1)).isEqualTo(CryptoUtils.toStringShort(BOC.getPublicKey()));
assertThat(results.getOtherResults().get(2)).isEqualTo("GBP");
assertThat(results.getOtherResults().get(3)).isEqualTo(300L);
- assertThat(results.getOtherResults().get(4)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_INFO.getPubkey()));
+ assertThat(results.getOtherResults().get(4)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_INFO.getPublicKey()));
assertThat(results.getOtherResults().get(5)).isEqualTo("GBP");
assertThat(results.getOtherResults().get(6)).isEqualTo(200L);
- assertThat(results.getOtherResults().get(7)).isEqualTo(CryptoUtils.toStringShort(BOC.getPubkey()));
+ assertThat(results.getOtherResults().get(7)).isEqualTo(CryptoUtils.toStringShort(BOC.getPublicKey()));
assertThat(results.getOtherResults().get(8)).isEqualTo("USD");
assertThat(results.getOtherResults().get(9)).isEqualTo(100L);
- assertThat(results.getOtherResults().get(10)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_INFO.getPubkey()));
+ assertThat(results.getOtherResults().get(10)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_INFO.getPublicKey()));
assertThat(results.getOtherResults().get(11)).isEqualTo("USD");
} catch (NoSuchFieldException e) {
diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt
index d2caa2f214..eb3e5ba532 100644
--- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt
+++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt
@@ -17,7 +17,7 @@ import net.corda.node.internal.configureDatabase
import net.corda.testing.TestIdentity
import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -52,7 +52,7 @@ class InteractiveShellTest {
override fun call() = a
}
- private val ids = makeTestIdentityService(listOf(megaCorp.identity))
+ private val ids = makeTestIdentityService(megaCorp.identity)
private val om = JacksonSupport.createInMemoryMapper(ids, YAMLFactory())
private fun check(input: String, expected: String) {
diff --git a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt
new file mode 100644
index 0000000000..0b9e42640c
--- /dev/null
+++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt
@@ -0,0 +1,78 @@
+package net.corda.node.internal
+
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.whenever
+import net.corda.core.utilities.OpaqueBytes
+import net.corda.core.utilities.getOrThrow
+import net.corda.finance.DOLLARS
+import net.corda.finance.flows.CashIssueFlow
+import net.corda.node.services.config.NotaryConfig
+import net.corda.nodeapi.internal.network.NetworkParameters
+import net.corda.nodeapi.internal.network.NetworkParametersCopier
+import net.corda.nodeapi.internal.network.NotaryInfo
+import net.corda.testing.ALICE_NAME
+import net.corda.testing.BOB_NAME
+import net.corda.testing.DUMMY_NOTARY_NAME
+import net.corda.testing.chooseIdentity
+import net.corda.testing.common.internal.testNetworkParameters
+import net.corda.testing.node.*
+import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.After
+import org.junit.Test
+import java.nio.file.Path
+import kotlin.test.assertFails
+
+class NetworkParametersTest {
+ private val mockNet = MockNetwork(
+ emptyList(),
+ MockNetworkParameters(networkSendManuallyPumped = true),
+ notarySpecs = listOf(MockNetwork.NotarySpec(DUMMY_NOTARY_NAME)))
+
+ @After
+ fun tearDown() {
+ mockNet.stopNodes()
+ }
+
+ // Minimum Platform Version tests
+ @Test
+ fun `node shutdowns when on lower platform version than network`() {
+ val alice = mockNet.createUnstartedNode(MockNodeParameters(legalName = ALICE_NAME, forcedID = 100, version = MockServices.MOCK_VERSION_INFO.copy(platformVersion = 1)))
+ val aliceDirectory = mockNet.baseDirectory(100)
+ val netParams = testNetworkParameters(
+ notaries = listOf(NotaryInfo(mockNet.defaultNotaryIdentity, true)),
+ minimumPlatformVersion = 2)
+ dropParametersToDir(aliceDirectory, netParams)
+ assertThatThrownBy { alice.start() }.hasMessageContaining("platform version")
+ }
+
+ @Test
+ fun `node works fine when on higher platform version`() {
+ val alice = mockNet.createUnstartedNode(MockNodeParameters(legalName = ALICE_NAME, forcedID = 100, version = MockServices.MOCK_VERSION_INFO.copy(platformVersion = 2)))
+ val aliceDirectory = mockNet.baseDirectory(100)
+ val netParams = testNetworkParameters(
+ notaries = listOf(NotaryInfo(mockNet.defaultNotaryIdentity, true)),
+ minimumPlatformVersion = 1)
+ dropParametersToDir(aliceDirectory, netParams)
+ alice.start()
+ }
+
+ // Notaries tests
+ @Test
+ fun `choosing notary not specified in network parameters will fail`() {
+ val fakeNotary = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME, configOverrides = {
+ val notary = NotaryConfig(false)
+ doReturn(notary).whenever(it).notary}))
+ val fakeNotaryId = fakeNotary.info.chooseIdentity()
+ val alice = mockNet.createPartyNode(ALICE_NAME)
+ assertThat(alice.services.networkMapCache.notaryIdentities).doesNotContain(fakeNotaryId)
+ assertFails {
+ alice.services.startFlow(CashIssueFlow(500.DOLLARS, OpaqueBytes.of(0x01), fakeNotaryId)).resultFuture.getOrThrow()
+ }
+ }
+
+ // Helpers
+ private fun dropParametersToDir(dir: Path, params: NetworkParameters) {
+ NetworkParametersCopier(params).install(dir)
+ }
+}
\ No newline at end of file
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 1965e3dc12..7eedfc2fec 100644
--- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt
+++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt
@@ -1,7 +1,9 @@
package net.corda.node.messaging
+import net.corda.core.messaging.AllPossibleRecipients
import net.corda.node.services.messaging.Message
import net.corda.node.services.messaging.TopicStringValidator
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
@@ -16,7 +18,7 @@ class InMemoryMessagingTests {
@Before
fun setUp() {
- mockNet = MockNetwork()
+ mockNet = MockNetwork(emptyList())
}
@After
@@ -72,7 +74,7 @@ class InMemoryMessagingTests {
var counter = 0
listOf(node1, node2, node3).forEach { it.network.addMessageHandler("test.topic") { _, _, _ -> counter++ } }
- node1.network.send(node2.network.createMessage("test.topic", data = bits), mockNet.messagingNetwork.everyoneOnline)
+ node1.network.send(node2.network.createMessage("test.topic", data = bits), rigorousMock())
mockNet.runNetwork(rounds = 1)
assertEquals(3, counter)
}
diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt
index cf5f84e301..6fa98122a5 100644
--- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt
+++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt
@@ -41,7 +41,12 @@ import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.checkpoints
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.*
-import net.corda.testing.contracts.VaultFiller
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.dsl.LedgerDSL
+import net.corda.testing.dsl.TestLedgerDSLInterpreter
+import net.corda.testing.dsl.TestTransactionDSLInterpreter
+import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
diff --git a/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt
index 2866d41dc4..be0d40ae8e 100644
--- a/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt
@@ -1,10 +1,19 @@
package net.corda.node.services
import net.corda.core.context.AuthServiceId
+import net.corda.core.flows.FlowLogic
+import net.corda.core.messaging.CordaRPCOps
+import net.corda.node.internal.security.Password
import net.corda.node.internal.security.RPCSecurityManagerImpl
+import net.corda.node.internal.security.tryAuthenticate
import net.corda.nodeapi.internal.config.User
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
+import javax.security.auth.login.FailedLoginException
+import kotlin.reflect.KFunction
+import kotlin.test.assertFails
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNull
class RPCSecurityManagerTest {
@@ -15,7 +24,147 @@ class RPCSecurityManagerTest {
assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#")
}
- private fun configWithRPCUsername(username: String) {
- RPCSecurityManagerImpl.fromUserList(users = listOf(User(username, "password", setOf())), id = AuthServiceId("TEST"))
+ @Test
+ fun `Generic RPC call authorization`() {
+ checkUserPermissions(
+ permitted = setOf(arrayListOf("nodeInfo"), arrayListOf("notaryIdentities")),
+ permissions = setOf(
+ Permissions.invokeRpc(CordaRPCOps::nodeInfo),
+ Permissions.invokeRpc(CordaRPCOps::notaryIdentities)))
}
+
+ @Test
+ fun `Flow invocation authorization`() {
+ checkUserPermissions(
+ permissions = setOf(Permissions.startFlow()),
+ permitted = setOf(
+ arrayListOf("startTrackedFlowDynamic", "net.corda.node.services.RPCSecurityManagerTest\$DummyFlow"),
+ arrayListOf("startFlowDynamic", "net.corda.node.services.RPCSecurityManagerTest\$DummyFlow")))
+ }
+
+ @Test
+ fun `Check startFlow RPC permission implies startFlowDynamic`() {
+ checkUserPermissions(
+ permissions = setOf(Permissions.invokeRpc("startFlow")),
+ permitted = setOf(arrayListOf("startFlow"), arrayListOf("startFlowDynamic")))
+ }
+
+ @Test
+ fun `Check startTrackedFlow RPC permission implies startTrackedFlowDynamic`() {
+ checkUserPermissions(
+ permitted = setOf(arrayListOf("startTrackedFlow"), arrayListOf("startTrackedFlowDynamic")),
+ permissions = setOf(Permissions.invokeRpc("startTrackedFlow")))
+ }
+
+ @Test
+ fun `Admin authorization`() {
+ checkUserPermissions(
+ permissions = setOf("all"),
+ permitted = allActions.map { arrayListOf(it) }.toSet())
+ }
+
+ @Test
+ fun `Malformed permission strings`() {
+ assertMalformedPermission("bar")
+ assertMalformedPermission("InvokeRpc.nodeInfo.XXX")
+ assertMalformedPermission("")
+ assertMalformedPermission(".")
+ assertMalformedPermission("..")
+ assertMalformedPermission("startFlow")
+ assertMalformedPermission("startFlow.")
+ }
+
+ @Test
+ fun `Login with unknown user`() {
+ val userRealm = RPCSecurityManagerImpl.fromUserList(
+ users = listOf(User("user", "xxxx", emptySet())),
+ id = AuthServiceId("TEST"))
+ userRealm.authenticate("user", Password("xxxx"))
+ assertFailsWith(FailedLoginException::class, "Login with wrong password should fail") {
+ userRealm.authenticate("foo", Password("xxxx"))
+ }
+ assertNull(userRealm.tryAuthenticate("foo", Password("wrong")),
+ "Login with wrong password should fail")
+ }
+
+ @Test
+ fun `Login with wrong credentials`() {
+ val userRealm = RPCSecurityManagerImpl.fromUserList(
+ users = listOf(User("user", "password", emptySet())),
+ id = AuthServiceId("TEST"))
+ userRealm.authenticate("user", Password("password"))
+ assertFailsWith(FailedLoginException::class, "Login with wrong password should fail") {
+ userRealm.authenticate("user", Password("wrong"))
+ }
+ assertNull(userRealm.tryAuthenticate("user", Password("wrong")),
+ "Login with wrong password should fail")
+ }
+
+ @Test
+ fun `Build invalid subject`() {
+ val userRealm = RPCSecurityManagerImpl.fromUserList(
+ users = listOf(User("user", "password", emptySet())),
+ id = AuthServiceId("TEST"))
+ val subject = userRealm.buildSubject("foo")
+ for (action in allActions) {
+ assert(!subject.isPermitted(action)) {
+ "Invalid subject should not be allowed to call $action"
+ }
+ }
+ }
+
+ private fun configWithRPCUsername(username: String) {
+ RPCSecurityManagerImpl.fromUserList(
+ users = listOf(User(username, "password", setOf())), id = AuthServiceId("TEST"))
+ }
+
+ private fun checkUserPermissions(permissions: Set, permitted: Set>) {
+ val user = User(username = "user", password = "password", permissions = permissions)
+ val userRealms = RPCSecurityManagerImpl.fromUserList(users = listOf(user), id = AuthServiceId("TEST"))
+ val disabled = allActions.filter { !permitted.contains(listOf(it)) }
+ for (subject in listOf(
+ userRealms.authenticate("user", Password("password")),
+ userRealms.tryAuthenticate("user", Password("password"))!!,
+ userRealms.buildSubject("user"))) {
+ for (request in permitted) {
+ val call = request.first()
+ val args = request.drop(1).toTypedArray()
+ assert(subject.isPermitted(request.first(), *args)) {
+ "User ${subject.principal} should be permitted ${call} with target '${request.toList()}'"
+ }
+ if (args.isEmpty()) {
+ assert(subject.isPermitted(request.first(), "XXX")) {
+ "User ${subject.principal} should be permitted ${call} with any target"
+ }
+ }
+ }
+
+ disabled.forEach {
+ assert(!subject.isPermitted(it)) {
+ "Permissions $permissions should not allow to call $it"
+ }
+ }
+
+ disabled.filter { !permitted.contains(listOf(it, "foo")) }.forEach {
+ assert(!subject.isPermitted(it, "foo")) {
+ "Permissions $permissions should not allow to call $it with argument 'foo'"
+ }
+ }
+ }
+ }
+
+ private fun assertMalformedPermission(permission: String) {
+ assertFails {
+ RPCSecurityManagerImpl.fromUserList(
+ users = listOf(User("x", "x", setOf(permission))),
+ id = AuthServiceId("TEST"))
+ }
+ }
+
+ companion object {
+ private val allActions = CordaRPCOps::class.members.filterIsInstance>().map { it.name }.toSet() +
+ setOf("startFlow", "startTrackedFlow")
+ }
+
+ private abstract class DummyFlow : FlowLogic()
}
\ No newline at end of file
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 fddea92a22..3c3e356f12 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.flows.FlowLogicRefFactory
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
+import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SingletonSerializeAsToken
@@ -23,7 +24,6 @@ import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.services.api.MonitoringService
import net.corda.node.services.api.ServiceHubInternal
-import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.services.statemachine.StateMachineManager
@@ -31,10 +31,13 @@ import net.corda.node.services.statemachine.StateMachineManagerImpl
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.internal.configureDatabase
+import net.corda.node.services.api.NetworkMapCacheInternal
+import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.*
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.services.MockAttachmentStorage
@@ -43,7 +46,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import java.nio.file.Paths
import java.time.Clock
import java.time.Instant
import java.util.concurrent.CountDownLatch
@@ -53,7 +55,7 @@ import kotlin.test.assertTrue
class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
private companion object {
- val ALICE_KEY = TestIdentity(ALICE_NAME, 70).key
+ val ALICE_KEY = TestIdentity(ALICE_NAME, 70).keyPair
val DUMMY_IDENTITY_1 = getTestPartyAndCertificate(Party(CordaX500Name("Dummy", "Madrid", "ES"), generateKeyPair().public))
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val myInfo = NodeInfo(listOf(NetworkHostAndPort("mockHost", 30000)), listOf(DUMMY_IDENTITY_1), 1, serial = 1L)
@@ -98,14 +100,19 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
database = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock())
val identityService = makeTestIdentityService()
kms = MockKeyManagementService(identityService, ALICE_KEY)
- val configuration = testNodeConfiguration(Paths.get("."), CordaX500Name("Alice", "London", "GB"))
+ val configuration = rigorousMock().also {
+ doReturn(true).whenever(it).devMode
+ doReturn(null).whenever(it).devModeOptions
+ }
val validatedTransactions = MockTransactionStorage()
database.transaction {
services = rigorousMock().also {
doReturn(configuration).whenever(it).configuration
doReturn(MonitoringService(MetricRegistry())).whenever(it).monitoringService
doReturn(validatedTransactions).whenever(it).validatedTransactions
- doReturn(NetworkMapCacheImpl(MockNetworkMapCache(database), identityService)).whenever(it).networkMapCache
+ doReturn(rigorousMock().also {
+ doReturn(doneFuture(null)).whenever(it).nodeReady
+ }).whenever(it).networkMapCache
doReturn(myInfo).whenever(it).myInfo
doReturn(kms).whenever(it).keyManagementService
doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), MockAttachmentStorage())).whenever(it).cordappProvider
diff --git a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt
index 46a5dc9db0..09c1041c54 100644
--- a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt
@@ -13,6 +13,7 @@ import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.*
+import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -27,13 +28,17 @@ class InMemoryIdentityServiceTests {
val bob = TestIdentity(BOB_NAME, 80)
val ALICE get() = alice.party
val ALICE_IDENTITY get() = alice.identity
- val ALICE_PUBKEY get() = alice.pubkey
+ val ALICE_PUBKEY get() = alice.publicKey
val BOB get() = bob.party
val BOB_IDENTITY get() = bob.identity
- val BOB_PUBKEY get() = bob.pubkey
- fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities.toSet(), DEV_TRUST_ROOT)
+ val BOB_PUBKEY get() = bob.publicKey
+ fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_TRUST_ROOT)
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
+
@Test
fun `get all identities`() {
val service = createService()
@@ -94,18 +99,16 @@ class InMemoryIdentityServiceTests {
*/
@Test
fun `assert unknown anonymous key is unrecognised`() {
- withTestSerialization {
- val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
- val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
- val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
- val service = createService()
- // TODO: Generate certificate with an EdDSA key rather than ECDSA
- val identity = Party(rootCert.cert)
- val txIdentity = AnonymousParty(txKey.public)
+ val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
+ val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
+ val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
+ val service = createService()
+ // TODO: Generate certificate with an EdDSA key rather than ECDSA
+ val identity = Party(rootCert.cert)
+ val txIdentity = AnonymousParty(txKey.public)
- assertFailsWith {
- service.assertOwnership(identity, txIdentity)
- }
+ assertFailsWith {
+ service.assertOwnership(identity, txIdentity)
}
}
@@ -137,30 +140,28 @@ class InMemoryIdentityServiceTests {
*/
@Test
fun `assert ownership`() {
- withTestSerialization {
- val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
- val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
+ val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
+ val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
- // Now we have identities, construct the service and let it know about both
- val service = createService(alice, bob)
- service.verifyAndRegisterIdentity(anonymousAlice)
- service.verifyAndRegisterIdentity(anonymousBob)
+ // Now we have identities, construct the service and let it know about both
+ val service = createService(alice, bob)
+ service.verifyAndRegisterIdentity(anonymousAlice)
+ service.verifyAndRegisterIdentity(anonymousBob)
- // Verify that paths are verified
- service.assertOwnership(alice.party, anonymousAlice.party.anonymise())
- service.assertOwnership(bob.party, anonymousBob.party.anonymise())
- assertFailsWith {
- service.assertOwnership(alice.party, anonymousBob.party.anonymise())
- }
- assertFailsWith {
- service.assertOwnership(bob.party, anonymousAlice.party.anonymise())
- }
+ // Verify that paths are verified
+ service.assertOwnership(alice.party, anonymousAlice.party.anonymise())
+ service.assertOwnership(bob.party, anonymousBob.party.anonymise())
+ assertFailsWith {
+ service.assertOwnership(alice.party, anonymousBob.party.anonymise())
+ }
+ assertFailsWith {
+ service.assertOwnership(bob.party, anonymousAlice.party.anonymise())
+ }
- assertFailsWith {
- val owningKey = Crypto.decodePublicKey(DEV_CA.certificate.subjectPublicKeyInfo.encoded)
- val subject = CordaX500Name.build(DEV_CA.certificate.cert.subjectX500Principal)
- service.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise())
- }
+ assertFailsWith {
+ val owningKey = Crypto.decodePublicKey(DEV_CA.certificate.subjectPublicKeyInfo.encoded)
+ val subject = CordaX500Name.build(DEV_CA.certificate.cert.subjectX500Principal)
+ service.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise())
}
}
diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt
index 9b50b7f5b0..956bbbb4cf 100644
--- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt
@@ -21,6 +21,7 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie
import net.corda.testing.node.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
@@ -35,12 +36,15 @@ class PersistentIdentityServiceTests {
val bob = TestIdentity(BOB_NAME, 80)
val ALICE get() = alice.party
val ALICE_IDENTITY get() = alice.identity
- val ALICE_PUBKEY get() = alice.pubkey
+ val ALICE_PUBKEY get() = alice.publicKey
val BOB get() = bob.party
val BOB_IDENTITY get() = bob.identity
- val BOB_PUBKEY get() = bob.pubkey
+ val BOB_PUBKEY get() = bob.publicKey
}
+ @Rule
+ @JvmField
+ val testSerialization = SerializationEnvironmentRule()
private lateinit var database: CordaPersistence
private lateinit var identityService: IdentityService
@@ -138,17 +142,15 @@ class PersistentIdentityServiceTests {
*/
@Test
fun `assert unknown anonymous key is unrecognised`() {
- withTestSerialization {
- val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
- val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
- val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME)
- val identity = Party(rootCert.cert)
- val txIdentity = AnonymousParty(txKey.public)
+ val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
+ val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
+ val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME)
+ val identity = Party(rootCert.cert)
+ val txIdentity = AnonymousParty(txKey.public)
- assertFailsWith {
- database.transaction {
- identityService.assertOwnership(identity, txIdentity)
- }
+ assertFailsWith {
+ database.transaction {
+ identityService.assertOwnership(identity, txIdentity)
}
}
}
@@ -191,38 +193,36 @@ class PersistentIdentityServiceTests {
*/
@Test
fun `assert ownership`() {
- withTestSerialization {
- val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
- val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
+ val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
+ val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
+ database.transaction {
+ // Now we have identities, construct the service and let it know about both
+ identityService.verifyAndRegisterIdentity(anonymousAlice)
+ identityService.verifyAndRegisterIdentity(anonymousBob)
+ }
+
+ // Verify that paths are verified
+ database.transaction {
+ identityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
+ identityService.assertOwnership(bob.party, anonymousBob.party.anonymise())
+ }
+ assertFailsWith {
database.transaction {
- // Now we have identities, construct the service and let it know about both
- identityService.verifyAndRegisterIdentity(anonymousAlice)
- identityService.verifyAndRegisterIdentity(anonymousBob)
+ identityService.assertOwnership(alice.party, anonymousBob.party.anonymise())
}
-
- // Verify that paths are verified
+ }
+ assertFailsWith {
database.transaction {
- identityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
- identityService.assertOwnership(bob.party, anonymousBob.party.anonymise())
- }
- assertFailsWith {
- database.transaction {
- identityService.assertOwnership(alice.party, anonymousBob.party.anonymise())
- }
- }
- assertFailsWith {
- database.transaction {
- identityService.assertOwnership(bob.party, anonymousAlice.party.anonymise())
- }
+ identityService.assertOwnership(bob.party, anonymousAlice.party.anonymise())
}
+ }
- assertFailsWith {
- val owningKey = Crypto.decodePublicKey(DEV_CA.certificate.subjectPublicKeyInfo.encoded)
- database.transaction {
- val subject = CordaX500Name.build(DEV_CA.certificate.cert.subjectX500Principal)
- identityService.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise())
- }
+ assertFailsWith {
+ val owningKey = Crypto.decodePublicKey(DEV_CA.certificate.subjectPublicKeyInfo.encoded)
+ database.transaction {
+ val subject = CordaX500Name.build(DEV_CA.certificate.cert.subjectX500Principal)
+ identityService.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise())
}
}
}
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 35eb3810a3..9a90b4a1e8 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
@@ -1,5 +1,7 @@
package net.corda.node.services.messaging
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.context.AuthServiceId
import net.corda.core.crypto.generateKeyPair
import net.corda.core.concurrent.CordaFuture
@@ -7,21 +9,23 @@ import com.codahale.metrics.MetricRegistry
import net.corda.core.crypto.generateKeyPair
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.node.internal.configureDatabase
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.internal.security.RPCSecurityManagerImpl
+import net.corda.node.services.config.CertChainPolicyConfig
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
-import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.*
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.node.testNodeConfiguration
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
@@ -65,9 +69,17 @@ class ArtemisMessagingTests {
@Before
fun setUp() {
securityManager = RPCSecurityManagerImpl.fromUserList(users = emptyList(), id = AuthServiceId("TEST"))
- config = testNodeConfiguration(
- baseDirectory = temporaryFolder.root.toPath(),
- myLegalName = ALICE_NAME)
+ abstract class AbstractNodeConfiguration : NodeConfiguration
+ config = rigorousMock().also {
+ doReturn(temporaryFolder.root.toPath()).whenever(it).baseDirectory
+ doReturn(ALICE_NAME).whenever(it).myLegalName
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ doReturn("").whenever(it).exportJMXto
+ doReturn(emptyList()).whenever(it).certificateChainCheckPolicies
+ doReturn(5).whenever(it).messageRedeliveryDelaySeconds
+ doReturn(true).whenever(it).useAMQPBridges
+ }
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock())
@@ -159,7 +171,7 @@ class ArtemisMessagingTests {
return Pair(messagingClient, receivedMessages)
}
- private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort), platformVersion: Int = 1): P2PMessagingClient {
+ private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort), platformVersion: Int = 1, maxMessageSize: Int = MAX_MESSAGE_SIZE): P2PMessagingClient {
return database.transaction {
P2PMessagingClient(
config,
@@ -167,16 +179,16 @@ class ArtemisMessagingTests {
server,
identity.public,
ServiceAffinityExecutor("ArtemisMessagingTests", 1),
- database
- ).apply {
+ database,
+ maxMessageSize = maxMessageSize).apply {
config.configureWithDevSSLCertificate()
messagingClient = this
}
}
}
- private fun createMessagingServer(local: Int = serverPort, rpc: Int = rpcPort): ArtemisMessagingServer {
- return ArtemisMessagingServer(config, local, rpc, networkMapCache, securityManager).apply {
+ private fun createMessagingServer(local: Int = serverPort, rpc: Int = rpcPort, maxMessageSize: Int = MAX_MESSAGE_SIZE): ArtemisMessagingServer {
+ return ArtemisMessagingServer(config, local, rpc, networkMapCache, securityManager, maxMessageSize).apply {
config.configureWithDevSSLCertificate()
messagingServer = this
}
diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt
index 0597bfbd40..14f91e0178 100644
--- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt
@@ -13,7 +13,7 @@ import java.math.BigInteger
import kotlin.test.assertEquals
class NetworkMapCacheTest {
- private val mockNet = MockNetwork()
+ private val mockNet = MockNetwork(emptyList())
@After
fun teardown() {
diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt
index 734a1ec860..608f583f89 100644
--- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt
@@ -5,14 +5,13 @@ import net.corda.core.crypto.sha256
import net.corda.core.internal.cert
import net.corda.core.serialization.serialize
import net.corda.core.utilities.seconds
-import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
-import net.corda.testing.DEV_CA
+import net.corda.testing.ALICE_NAME
+import net.corda.testing.BOB_NAME
import net.corda.testing.DEV_TRUST_ROOT
-import net.corda.testing.ROOT_CA
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
-import net.corda.testing.node.network.NetworkMapServer
-import net.corda.testing.TestDependencyInjectionBase
+import net.corda.testing.internal.createNodeInfoAndSigned
+import net.corda.testing.node.internal.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
@@ -26,13 +25,12 @@ class NetworkMapClientTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
+
+ private val cacheTimeout = 100000.seconds
+
private lateinit var server: NetworkMapServer
private lateinit var networkMapClient: NetworkMapClient
- companion object {
- private val cacheTimeout = 100000.seconds
- }
-
@Before
fun setUp() {
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
@@ -47,9 +45,7 @@ class NetworkMapClientTest {
@Test
fun `registered node is added to the network map`() {
- // Create node info.
- val signedNodeInfo = createNodeInfo("Test1")
- val nodeInfo = signedNodeInfo.verified()
+ val (nodeInfo, signedNodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
networkMapClient.publish(signedNodeInfo)
@@ -58,8 +54,8 @@ class NetworkMapClientTest {
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash)
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
- val signedNodeInfo2 = createNodeInfo("Test2")
- val nodeInfo2 = signedNodeInfo2.verified()
+ val (nodeInfo2, signedNodeInfo2) = createNodeInfoAndSigned(BOB_NAME)
+
networkMapClient.publish(signedNodeInfo2)
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
@@ -72,7 +68,7 @@ class NetworkMapClientTest {
@Test
fun `download NetworkParameter correctly`() {
// The test server returns same network parameter for any hash.
- val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())
+ val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())?.verified()
assertNotNull(networkParameter)
assertEquals(NetworkMapServer.stubNetworkParameter, networkParameter)
}
diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt
index fc3542a3d6..b76c7d59d9 100644
--- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt
@@ -1,65 +1,70 @@
package net.corda.node.services.network
-import com.google.common.jimfs.Configuration
+import com.google.common.jimfs.Configuration.unix
import com.google.common.jimfs.Jimfs
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify
-import net.corda.cordform.CordformNode
-import net.corda.core.crypto.Crypto
+import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY
import net.corda.core.crypto.SecureHash
-import net.corda.core.crypto.SignedData
+import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.div
import net.corda.core.internal.uncheckedCast
-import net.corda.nodeapi.internal.NetworkMap
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
-import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.millis
import net.corda.node.services.api.NetworkMapCacheInternal
+import net.corda.nodeapi.internal.SignedNodeInfo
+import net.corda.nodeapi.internal.network.NetworkMap
+import net.corda.testing.ALICE_NAME
import net.corda.testing.SerializationEnvironmentRule
+import net.corda.testing.internal.TestNodeInfoBuilder
+import net.corda.testing.internal.createNodeInfoAndSigned
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.After
import org.junit.Rule
import org.junit.Test
import rx.schedulers.TestScheduler
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
-import kotlin.test.assertEquals
class NetworkMapUpdaterTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
- private val jimFs = Jimfs.newFileSystem(Configuration.unix())
- private val baseDir = jimFs.getPath("/node")
+
+ private val fs = Jimfs.newFileSystem(unix())
+ private val baseDir = fs.getPath("/node")
+ private val networkMapCache = createMockNetworkMapCache()
+ private val nodeInfoMap = ConcurrentHashMap()
+ private val cacheExpiryMs = 100
+ private val networkMapClient = createMockNetworkMapClient()
+ private val scheduler = TestScheduler()
+ private val networkParametersHash = SecureHash.randomSHA256()
+ private val fileWatcher = NodeInfoWatcher(baseDir, scheduler)
+ private val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, networkParametersHash)
+ private val nodeInfoBuilder = TestNodeInfoBuilder()
+
+ @After
+ fun cleanUp() {
+ updater.close()
+ fs.close()
+ }
@Test
fun `publish node info`() {
- val keyPair = Crypto.generateKeyPair()
+ nodeInfoBuilder.addIdentity(ALICE_NAME)
- val nodeInfo1 = TestNodeInfoFactory.createNodeInfo("Info 1").verified()
- val signedNodeInfo = TestNodeInfoFactory.sign(keyPair, nodeInfo1)
-
- val sameNodeInfoDifferentTime = nodeInfo1.copy(serial = System.currentTimeMillis())
- val signedSameNodeInfoDifferentTime = TestNodeInfoFactory.sign(keyPair, sameNodeInfoDifferentTime)
-
- val differentNodeInfo = nodeInfo1.copy(addresses = listOf(NetworkHostAndPort("my.new.host.com", 1000)))
- val signedDifferentNodeInfo = TestNodeInfoFactory.sign(keyPair, differentNodeInfo)
-
- val networkMapCache = getMockNetworkMapCache()
-
- val networkMapClient = mock()
-
- val scheduler = TestScheduler()
- val fileWatcher = NodeInfoWatcher(baseDir, scheduler)
- val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient)
+ val (nodeInfo1, signedNodeInfo1) = nodeInfoBuilder.buildWithSigned()
+ val (sameNodeInfoDifferentTime, signedSameNodeInfoDifferentTime) = nodeInfoBuilder.buildWithSigned(serial = System.currentTimeMillis())
// Publish node info for the first time.
- updater.updateNodeInfo(nodeInfo1) { signedNodeInfo }
+ updater.updateNodeInfo(nodeInfo1) { signedNodeInfo1 }
// Sleep as publish is asynchronous.
// TODO: Remove sleep in unit test
- Thread.sleep(200)
+ Thread.sleep(2L * cacheExpiryMs)
verify(networkMapClient, times(1)).publish(any())
networkMapCache.addNode(nodeInfo1)
@@ -67,167 +72,144 @@ class NetworkMapUpdaterTest {
// Publish the same node info, but with different serial.
updater.updateNodeInfo(sameNodeInfoDifferentTime) { signedSameNodeInfoDifferentTime }
// TODO: Remove sleep in unit test.
- Thread.sleep(200)
+ Thread.sleep(2L * cacheExpiryMs)
// Same node info should not publish twice
verify(networkMapClient, times(0)).publish(signedSameNodeInfoDifferentTime)
+ val (differentNodeInfo, signedDifferentNodeInfo) = createNodeInfoAndSigned("Bob")
+
// Publish different node info.
updater.updateNodeInfo(differentNodeInfo) { signedDifferentNodeInfo }
// TODO: Remove sleep in unit test.
Thread.sleep(200)
verify(networkMapClient, times(1)).publish(signedDifferentNodeInfo)
-
- updater.close()
}
@Test
fun `process add node updates from network map, with additional node infos from dir`() {
- val nodeInfo1 = TestNodeInfoFactory.createNodeInfo("Info 1")
- val nodeInfo2 = TestNodeInfoFactory.createNodeInfo("Info 2")
- val nodeInfo3 = TestNodeInfoFactory.createNodeInfo("Info 3")
- val nodeInfo4 = TestNodeInfoFactory.createNodeInfo("Info 4")
- val fileNodeInfo = TestNodeInfoFactory.createNodeInfo("Info from file")
- val networkMapCache = getMockNetworkMapCache()
-
- val nodeInfoMap = ConcurrentHashMap>()
- val networkMapClient = mock {
- on { publish(any()) }.then {
- val signedNodeInfo: SignedData = uncheckedCast(it.arguments.first())
- nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
- }
- on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
- on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
- }
-
- val scheduler = TestScheduler()
- val fileWatcher = NodeInfoWatcher(baseDir, scheduler)
- val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient)
+ val (nodeInfo1, signedNodeInfo1) = createNodeInfoAndSigned("Info 1")
+ val (nodeInfo2, signedNodeInfo2) = createNodeInfoAndSigned("Info 2")
+ val (nodeInfo3, signedNodeInfo3) = createNodeInfoAndSigned("Info 3")
+ val (nodeInfo4, signedNodeInfo4) = createNodeInfoAndSigned("Info 4")
+ val (fileNodeInfo, signedFileNodeInfo) = createNodeInfoAndSigned("Info from file")
// Test adding new node.
- networkMapClient.publish(nodeInfo1)
+ networkMapClient.publish(signedNodeInfo1)
// Not subscribed yet.
verify(networkMapCache, times(0)).addNode(any())
updater.subscribeToNetworkMap()
- networkMapClient.publish(nodeInfo2)
+ networkMapClient.publish(signedNodeInfo2)
// TODO: Remove sleep in unit test.
- Thread.sleep(200)
+ Thread.sleep(2L * cacheExpiryMs)
verify(networkMapCache, times(2)).addNode(any())
- verify(networkMapCache, times(1)).addNode(nodeInfo1.verified())
- verify(networkMapCache, times(1)).addNode(nodeInfo2.verified())
+ verify(networkMapCache, times(1)).addNode(nodeInfo1)
+ verify(networkMapCache, times(1)).addNode(nodeInfo2)
- NodeInfoWatcher.saveToFile(baseDir / CordformNode.NODE_INFO_DIRECTORY, fileNodeInfo)
- networkMapClient.publish(nodeInfo3)
- networkMapClient.publish(nodeInfo4)
+ NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, signedFileNodeInfo)
+ networkMapClient.publish(signedNodeInfo3)
+ networkMapClient.publish(signedNodeInfo4)
scheduler.advanceTimeBy(10, TimeUnit.SECONDS)
// TODO: Remove sleep in unit test.
- Thread.sleep(200)
+ Thread.sleep(2L * cacheExpiryMs)
// 4 node info from network map, and 1 from file.
verify(networkMapCache, times(5)).addNode(any())
- verify(networkMapCache, times(1)).addNode(nodeInfo3.verified())
- verify(networkMapCache, times(1)).addNode(nodeInfo4.verified())
- verify(networkMapCache, times(1)).addNode(fileNodeInfo.verified())
-
- updater.close()
+ verify(networkMapCache, times(1)).addNode(nodeInfo3)
+ verify(networkMapCache, times(1)).addNode(nodeInfo4)
+ verify(networkMapCache, times(1)).addNode(fileNodeInfo)
}
@Test
fun `process remove node updates from network map, with additional node infos from dir`() {
- val nodeInfo1 = TestNodeInfoFactory.createNodeInfo("Info 1")
- val nodeInfo2 = TestNodeInfoFactory.createNodeInfo("Info 2")
- val nodeInfo3 = TestNodeInfoFactory.createNodeInfo("Info 3")
- val nodeInfo4 = TestNodeInfoFactory.createNodeInfo("Info 4")
- val fileNodeInfo = TestNodeInfoFactory.createNodeInfo("Info from file")
- val networkMapCache = getMockNetworkMapCache()
-
- val nodeInfoMap = ConcurrentHashMap>()
- val networkMapClient = mock {
- on { publish(any()) }.then {
- val signedNodeInfo: SignedData = uncheckedCast(it.arguments.first())
- nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
- }
- on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
- on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
- }
-
- val scheduler = TestScheduler()
- val fileWatcher = NodeInfoWatcher(baseDir, scheduler)
- val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient)
+ val (nodeInfo1, signedNodeInfo1) = createNodeInfoAndSigned("Info 1")
+ val (nodeInfo2, signedNodeInfo2) = createNodeInfoAndSigned("Info 2")
+ val (nodeInfo3, signedNodeInfo3) = createNodeInfoAndSigned("Info 3")
+ val (nodeInfo4, signedNodeInfo4) = createNodeInfoAndSigned("Info 4")
+ val (fileNodeInfo, signedFileNodeInfo) = createNodeInfoAndSigned("Info from file")
// Add all nodes.
- NodeInfoWatcher.saveToFile(baseDir / CordformNode.NODE_INFO_DIRECTORY, fileNodeInfo)
- networkMapClient.publish(nodeInfo1)
- networkMapClient.publish(nodeInfo2)
- networkMapClient.publish(nodeInfo3)
- networkMapClient.publish(nodeInfo4)
+ NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, signedFileNodeInfo)
+ networkMapClient.publish(signedNodeInfo1)
+ networkMapClient.publish(signedNodeInfo2)
+ networkMapClient.publish(signedNodeInfo3)
+ networkMapClient.publish(signedNodeInfo4)
updater.subscribeToNetworkMap()
scheduler.advanceTimeBy(10, TimeUnit.SECONDS)
// TODO: Remove sleep in unit test.
- Thread.sleep(200)
+ Thread.sleep(2L * cacheExpiryMs)
// 4 node info from network map, and 1 from file.
- assertEquals(4, nodeInfoMap.size)
+ assertThat(nodeInfoMap).hasSize(4)
verify(networkMapCache, times(5)).addNode(any())
- verify(networkMapCache, times(1)).addNode(fileNodeInfo.verified())
+ verify(networkMapCache, times(1)).addNode(fileNodeInfo)
// Test remove node.
nodeInfoMap.clear()
// TODO: Remove sleep in unit test.
- Thread.sleep(200)
+ Thread.sleep(2L * cacheExpiryMs)
verify(networkMapCache, times(4)).removeNode(any())
- verify(networkMapCache, times(1)).removeNode(nodeInfo1.verified())
- verify(networkMapCache, times(1)).removeNode(nodeInfo2.verified())
- verify(networkMapCache, times(1)).removeNode(nodeInfo3.verified())
- verify(networkMapCache, times(1)).removeNode(nodeInfo4.verified())
+ verify(networkMapCache, times(1)).removeNode(nodeInfo1)
+ verify(networkMapCache, times(1)).removeNode(nodeInfo2)
+ verify(networkMapCache, times(1)).removeNode(nodeInfo3)
+ verify(networkMapCache, times(1)).removeNode(nodeInfo4)
// Node info from file should not be deleted
- assertEquals(1, networkMapCache.allNodeHashes.size)
- assertEquals(fileNodeInfo.verified().serialize().hash, networkMapCache.allNodeHashes.first())
-
- updater.close()
+ assertThat(networkMapCache.allNodeHashes).containsOnly(fileNodeInfo.serialize().hash)
}
@Test
fun `receive node infos from directory, without a network map`() {
- val fileNodeInfo = TestNodeInfoFactory.createNodeInfo("Info from file")
-
- val networkMapCache = getMockNetworkMapCache()
-
- val scheduler = TestScheduler()
- val fileWatcher = NodeInfoWatcher(baseDir, scheduler)
- val updater = NetworkMapUpdater(networkMapCache, fileWatcher, null)
+ val (fileNodeInfo, signedFileNodeInfo) = createNodeInfoAndSigned("Info from file")
// Not subscribed yet.
verify(networkMapCache, times(0)).addNode(any())
updater.subscribeToNetworkMap()
- NodeInfoWatcher.saveToFile(baseDir / CordformNode.NODE_INFO_DIRECTORY, fileNodeInfo)
+ NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, signedFileNodeInfo)
scheduler.advanceTimeBy(10, TimeUnit.SECONDS)
verify(networkMapCache, times(1)).addNode(any())
- verify(networkMapCache, times(1)).addNode(fileNodeInfo.verified())
+ verify(networkMapCache, times(1)).addNode(fileNodeInfo)
- assertEquals(1, networkMapCache.allNodeHashes.size)
- assertEquals(fileNodeInfo.verified().serialize().hash, networkMapCache.allNodeHashes.first())
-
- updater.close()
+ assertThat(networkMapCache.allNodeHashes).containsOnly(fileNodeInfo.serialize().hash)
}
- private fun getMockNetworkMapCache() = mock {
- val data = ConcurrentHashMap()
- on { addNode(any()) }.then {
- val nodeInfo = it.arguments.first() as NodeInfo
- data.put(nodeInfo.legalIdentities.first(), nodeInfo)
+ private fun createMockNetworkMapClient(): NetworkMapClient {
+ return mock {
+ on { publish(any()) }.then {
+ val signedNodeInfo: SignedNodeInfo = uncheckedCast(it.arguments[0])
+ nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
+ }
+ on { getNetworkMap() }.then {
+ NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), networkParametersHash), cacheExpiryMs.millis)
+ }
+ on { getNodeInfo(any()) }.then {
+ nodeInfoMap[it.arguments[0]]?.verified()
+ }
}
- on { removeNode(any()) }.then { data.remove((it.arguments.first() as NodeInfo).legalIdentities.first()) }
- on { getNodeByLegalIdentity(any()) }.then { data[it.arguments.first()] }
- on { allNodeHashes }.then { data.values.map { it.serialize().hash } }
- on { getNodeByHash(any()) }.then { mock -> data.values.single { it.serialize().hash == mock.arguments.first() } }
+ }
+
+ private fun createMockNetworkMapCache(): NetworkMapCacheInternal {
+ return mock {
+ val data = ConcurrentHashMap()
+ on { addNode(any()) }.then {
+ val nodeInfo = it.arguments[0] as NodeInfo
+ data.put(nodeInfo.legalIdentities[0], nodeInfo)
+ }
+ on { removeNode(any()) }.then { data.remove((it.arguments[0] as NodeInfo).legalIdentities[0]) }
+ on { getNodeByLegalIdentity(any()) }.then { data[it.arguments[0]] }
+ on { allNodeHashes }.then { data.values.map { it.serialize().hash } }
+ on { getNodeByHash(any()) }.then { mock -> data.values.single { it.serialize().hash == mock.arguments[0] } }
+ }
+ }
+
+ private fun createNodeInfoAndSigned(org: String): Pair {
+ return createNodeInfoAndSigned(CordaX500Name(org, "London", "GB"))
}
}
\ No newline at end of file
diff --git a/node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt b/node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt
deleted file mode 100644
index 975bd65bf4..0000000000
--- a/node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package net.corda.node.services.network
-
-import net.corda.core.crypto.Crypto
-import net.corda.core.crypto.DigitalSignature
-import net.corda.core.crypto.SignedData
-import net.corda.core.identity.CordaX500Name
-import net.corda.core.identity.PartyAndCertificate
-import net.corda.core.node.NodeInfo
-import net.corda.core.serialization.serialize
-import net.corda.core.utilities.NetworkHostAndPort
-import net.corda.nodeapi.internal.crypto.CertificateType
-import net.corda.nodeapi.internal.crypto.X509CertificateFactory
-import net.corda.nodeapi.internal.crypto.X509Utilities
-import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.cert.X509CertificateHolder
-import java.security.KeyPair
-import java.security.cert.CertPath
-import java.security.cert.Certificate
-import java.security.cert.X509Certificate
-
-object TestNodeInfoFactory {
- private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
- private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey)
- private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
- private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
-
- fun createNodeInfo(organisation: String): SignedData {
- val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
- val clientCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public)
- val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
- val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.$organisation.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L)
- return sign(keyPair, nodeInfo)
- }
-
- fun sign(keyPair: KeyPair, t: T): SignedData {
- // Create digital signature.
- val digitalSignature = DigitalSignature.WithKey(keyPair.public, Crypto.doSign(keyPair.private, t.serialize().bytes))
- return SignedData(t.serialize(), digitalSignature)
- }
-
- private fun buildCertPath(vararg certificates: Certificate): CertPath {
- return X509CertificateFactory().generateCertPath(*certificates)
- }
-
- private fun X509CertificateHolder.toX509Certificate(): X509Certificate {
- return X509CertificateFactory().generateCertificate(encoded.inputStream())
- }
-
-}
\ No newline at end of file
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 d1c6820fd7..0a711088b8 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
@@ -3,7 +3,6 @@ package net.corda.node.services.persistence
import net.corda.core.context.InvocationContext
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
-import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
@@ -15,7 +14,10 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.*
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
+import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
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 e495f12f72..3f559113da 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
@@ -13,6 +13,8 @@ import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.*
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
@@ -24,7 +26,7 @@ import kotlin.test.assertEquals
class DBTransactionStorageTests {
private companion object {
- val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).pubkey
+ val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
}
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 f2e1e82d45..7844d0e52d 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
@@ -37,12 +37,12 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.testing.*
-import net.corda.testing.contracts.*
+import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.schemas.DummyDealStateSchemaV1
-import net.corda.testing.schemas.DummyLinearStateSchemaV1
-import net.corda.testing.schemas.DummyLinearStateSchemaV2
+import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
+import net.corda.testing.internal.vault.DummyLinearStateSchemaV2
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.hibernate.SessionFactory
@@ -62,7 +62,7 @@ class HibernateConfigurationTest {
val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10)
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val BOC get() = bankOfCorda.party
- val BOC_KEY get() = bankOfCorda.key
+ val BOC_KEY get() = bankOfCorda.keyPair
}
@Rule
@@ -93,7 +93,7 @@ class HibernateConfigurationTest {
@Before
fun setUp() {
- val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset")
+ val cordappPackages = listOf("net.corda.testing.internal.vault", "net.corda.finance.contracts.asset")
bankServices = MockServices(cordappPackages, rigorousMock(), BOC.name, BOC_KEY)
issuerServices = MockServices(cordappPackages, rigorousMock(), dummyCashIssuer)
notaryServices = MockServices(cordappPackages, rigorousMock(), dummyNotary)
@@ -113,7 +113,7 @@ class HibernateConfigurationTest {
// `consumeCash` expects we can self-notarise transactions
services = object : MockServices(cordappPackages, rigorousMock().also {
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME })
- }, BOB_NAME, generateKeyPair(), dummyNotary.key) {
+ }, BOB_NAME, generateKeyPair(), dummyNotary.keyPair) {
override val vaultService = makeVaultService(database.hibernateConfig, schemaService)
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) {
for (stx in txs) {
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt
index d21cd6d6db..5995f3c177 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt
@@ -17,9 +17,9 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
-import net.corda.testing.LogHelper
+import net.corda.testing.internal.LogHelper
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.junit.After
import org.junit.Before
import org.junit.Ignore
diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt
index 9ad3ec390c..dcda96ce30 100644
--- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt
@@ -16,11 +16,11 @@ import net.corda.node.services.api.SchemaService
import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.DatabaseTransactionManager
-import net.corda.testing.LogHelper
+import net.corda.testing.internal.LogHelper
import net.corda.testing.TestIdentity
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.junit.After
import org.junit.Before
import org.junit.Test
diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt
index aa0d9253cd..ac9a88afe0 100644
--- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt
@@ -12,7 +12,7 @@ import net.corda.node.services.api.ServiceHubInternal
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.MockNetwork
-import net.corda.testing.schemas.DummyLinearStateSchemaV1
+import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
import org.hibernate.annotations.Cascade
import org.hibernate.annotations.CascadeType
import org.junit.Test
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 a4737f44fa..1fe97568a8 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
@@ -32,6 +32,7 @@ import net.corda.node.services.persistence.checkpoints
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
+import net.corda.testing.internal.LogHelper
import net.corda.testing.node.InMemoryMessagingNetwork.MessageTransfer
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
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 57beddeb10..cd2e965712 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
@@ -13,11 +13,11 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
-import net.corda.testing.LogHelper
+import net.corda.testing.internal.LogHelper
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.freeLocalHostAndPort
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.junit.After
import org.junit.Before
import org.junit.Rule
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 1c26932b19..f4d03c9c39 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
@@ -7,6 +7,8 @@ import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.*
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.junit.After
import org.junit.Before
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 0a281f0713..bb7a01c77a 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
@@ -31,7 +31,9 @@ import net.corda.finance.utils.sumCash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.*
-import net.corda.testing.contracts.VaultFiller
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat
@@ -64,8 +66,8 @@ class NodeVaultServiceTest {
val DUMMY_NOTARY get() = dummyNotary.party
val DUMMY_NOTARY_IDENTITY get() = dummyNotary.identity
val MEGA_CORP get() = megaCorp.party
- val MEGA_CORP_KEY get() = megaCorp.key
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+ val MEGA_CORP_KEY get() = megaCorp.keyPair
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MEGA_CORP_IDENTITY get() = megaCorp.identity
val MINI_CORP get() = miniCorp.party
val MINI_CORP_IDENTITY get() = miniCorp.identity
@@ -86,10 +88,9 @@ class NodeVaultServiceTest {
fun setUp() {
LogHelper.setLevel(NodeVaultService::class)
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
- listOf(MEGA_CORP_KEY),
- makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
cordappPackages,
- MEGA_CORP.name)
+ makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
+ megaCorp)
database = databaseAndServices.first
services = databaseAndServices.second
vaultFiller = VaultFiller(services, dummyNotary)
@@ -136,7 +137,7 @@ class NodeVaultServiceTest {
assertThat(w1).hasSize(3)
val originalVault = vaultService
- val services2 = object : MockServices(rigorousMock(), MEGA_CORP.name) {
+ val services2 = object : MockServices(emptyList(), rigorousMock(), MEGA_CORP.name) {
override val vaultService: NodeVaultService get() = originalVault
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) {
for (stx in txs) {
@@ -586,7 +587,7 @@ class NodeVaultServiceTest {
val identity = services.myInfo.singleIdentityAndCert()
assertEquals(services.identityService.partyFromKey(identity.owningKey), identity.party)
val anonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false)
- val thirdPartyServices = MockServices(rigorousMock().also {
+ val thirdPartyServices = MockServices(emptyList(), rigorousMock().also {
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MEGA_CORP.name })
}, MEGA_CORP.name)
val thirdPartyIdentity = thirdPartyServices.keyManagementService.freshKeyAndCert(thirdPartyServices.myInfo.singleIdentityAndCert(), false)
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 2f75c95959..1a977695eb 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
@@ -26,12 +26,15 @@ import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.*
-import net.corda.testing.contracts.*
+import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.DUMMY_LINEAR_CONTRACT_PROGRAM_ID
+import net.corda.testing.internal.vault.DummyLinearContract
+import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestIdentityService
import net.corda.testing.schemas.DummyDealStateSchemaV1
-import net.corda.testing.schemas.DummyLinearStateSchemaV1
+import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.*
@@ -65,17 +68,17 @@ open class VaultQueryTests {
val BOB_IDENTITY get() = bob.identity
val BOC get() = bankOfCorda.party
val BOC_IDENTITY get() = bankOfCorda.identity
- val BOC_KEY get() = bankOfCorda.key
- val BOC_PUBKEY get() = bankOfCorda.pubkey
+ val BOC_KEY get() = bankOfCorda.keyPair
+ val BOC_PUBKEY get() = bankOfCorda.publicKey
val CASH_NOTARY get() = cashNotary.party
val CASH_NOTARY_IDENTITY get() = cashNotary.identity
val CHARLIE get() = charlie.party
val CHARLIE_IDENTITY get() = charlie.identity
val DUMMY_NOTARY get() = dummyNotary.party
- val DUMMY_NOTARY_KEY get() = dummyNotary.key
+ val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
val MEGA_CORP_IDENTITY get() = megaCorp.identity
- val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
- val MEGA_CORP_KEY get() = megaCorp.key
+ val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
+ val MEGA_CORP_KEY get() = megaCorp.keyPair
val MEGA_CORP get() = megaCorp.party
val MINI_CORP_IDENTITY get() = miniCorp.identity
val MINI_CORP get() = miniCorp.party
@@ -105,15 +108,15 @@ open class VaultQueryTests {
open fun setUp() {
// register additional identities
val databaseAndServices = makeTestDatabaseAndMockServices(
- listOf(MEGA_CORP_KEY, DUMMY_NOTARY_KEY),
- makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)),
cordappPackages,
- MEGA_CORP.name)
+ makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity),
+ megaCorp,
+ DUMMY_NOTARY_KEY)
database = databaseAndServices.first
services = databaseAndServices.second
vaultFiller = VaultFiller(services, dummyNotary)
vaultFillerCashNotary = VaultFiller(services, dummyNotary, CASH_NOTARY)
- notaryServices = MockServices(cordappPackages, rigorousMock(), dummyNotary, dummyCashIssuer.key, BOC_KEY, MEGA_CORP_KEY)
+ notaryServices = MockServices(cordappPackages, rigorousMock(), dummyNotary, dummyCashIssuer.keyPair, BOC_KEY, MEGA_CORP_KEY)
identitySvc = services.identityService
// Register all of the identities we're going to use
(notaryServices.myInfo.legalIdentitiesAndCerts + BOC_IDENTITY + CASH_NOTARY_IDENTITY + MINI_CORP_IDENTITY + MEGA_CORP_IDENTITY).forEach { identity ->
diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt
index 66f4973877..f55943c9f6 100644
--- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt
@@ -28,7 +28,7 @@ import net.corda.node.services.api.VaultServiceInternal
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.testing.chooseIdentity
import net.corda.testing.node.MockNetwork
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.startFlow
import org.junit.After
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 c2e074f9c5..70430f4f16 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
@@ -21,11 +21,12 @@ import net.corda.finance.contracts.getCashBalance
import net.corda.finance.schemas.CashSchemaV1
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.*
-import net.corda.testing.contracts.*
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.*
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestIdentityService
-import net.corda.testing.schemas.DummyLinearStateSchemaV1
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
@@ -41,7 +42,7 @@ import kotlin.test.fail
class VaultWithCashTest {
private companion object {
private val cordappPackages = listOf(
- "net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName, DummyLinearStateSchemaV1::class.packageName)
+ "net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName, DummyLinearStateSchemaV1::class.packageName)
val BOB = TestIdentity(BOB_NAME, 80).party
val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10)
val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1)
@@ -51,13 +52,14 @@ class VaultWithCashTest {
val DUMMY_NOTARY get() = dummyNotary.party
val MEGA_CORP get() = megaCorp.party
val MEGA_CORP_IDENTITY get() = megaCorp.identity
- val MEGA_CORP_KEY get() = megaCorp.key
+ val MEGA_CORP_KEY get() = megaCorp.keyPair
val MINI_CORP_IDENTITY get() = miniCorp.identity
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
+ private val servicesKey = generateKeyPair()
lateinit var services: MockServices
private lateinit var vaultFiller: VaultFiller
lateinit var issuerServices: MockServices
@@ -70,10 +72,10 @@ class VaultWithCashTest {
fun setUp() {
LogHelper.setLevel(VaultWithCashTest::class)
val databaseAndServices = makeTestDatabaseAndMockServices(
- listOf(generateKeyPair(), dummyNotary.key),
- makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)),
cordappPackages,
- MEGA_CORP.name)
+ makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity),
+ TestIdentity(MEGA_CORP.name, servicesKey),
+ dummyNotary.keyPair)
database = databaseAndServices.first
services = databaseAndServices.second
vaultFiller = VaultFiller(services, dummyNotary)
@@ -100,8 +102,7 @@ class VaultWithCashTest {
val state = w[0].state.data
assertEquals(30.45.DOLLARS `issued by` DUMMY_CASH_ISSUER, state.amount)
- assertEquals(services.key.public, state.owner.owningKey)
-
+ assertEquals(servicesKey.public, state.owner.owningKey)
assertEquals(34.70.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w[2].state.data).amount)
assertEquals(34.85.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w[1].state.data).amount)
}
diff --git a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt
index f48a38ac0c..91210f052a 100644
--- a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt
+++ b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt
@@ -6,7 +6,7 @@ import net.corda.core.internal.tee
import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.*
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.rigorousMock
+import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Test
diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt
index ec8d1c961d..50922e9fde 100644
--- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt
+++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt
@@ -14,8 +14,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.testing.ALICE_NAME
-import net.corda.testing.rigorousMock
-import net.corda.testing.node.testNodeConfiguration
+import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
@@ -45,7 +44,14 @@ class NetworkRegistrationHelperTest {
@Before
fun init() {
- config = testNodeConfiguration(baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE_NAME)
+ abstract class AbstractNodeConfiguration : NodeConfiguration
+ config = rigorousMock().also {
+ doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory
+ doReturn("trustpass").whenever(it).trustStorePassword
+ doReturn("cordacadevpass").whenever(it).keyStorePassword
+ doReturn(ALICE_NAME).whenever(it).myLegalName
+ doReturn("").whenever(it).emailAddress
+ }
}
@Test
diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt
index 11d6e8ac1b..1cbbadd253 100644
--- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt
+++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt
@@ -19,6 +19,9 @@ import net.corda.node.internal.configureDatabase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.*
+import net.corda.testing.internal.withoutTestSerialization
+import net.corda.testing.internal.LogHelper
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.*
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.junit.After
@@ -38,7 +41,7 @@ class NodeInterestRatesTest {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val MEGA_CORP_KEY = generateKeyPair()
val ALICE get() = alice.party
- val ALICE_PUBKEY get() = alice.pubkey
+ val ALICE_PUBKEY get() = alice.publicKey
}
@Rule
diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt
index 262681c0c6..b0d72e4917 100644
--- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt
+++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt
@@ -25,6 +25,8 @@ import net.corda.finance.contracts.PaymentRule
import net.corda.finance.contracts.Tenor
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.*
+import net.corda.testing.dsl.*
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import net.corda.testing.node.transaction
@@ -39,14 +41,14 @@ private val DUMMY_PARTY = Party(CordaX500Name("Dummy", "Madrid", "ES"), generate
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
private val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
-private val ORACLE_PUBKEY = TestIdentity(CordaX500Name("Oracle", "London", "GB")).pubkey
+private val ORACLE_PUBKEY = TestIdentity(CordaX500Name("Oracle", "London", "GB")).publicKey
private val DUMMY_NOTARY get() = dummyNotary.party
-private val DUMMY_NOTARY_KEY get() = dummyNotary.key
+private val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
private val MEGA_CORP get() = megaCorp.party
-private val MEGA_CORP_KEY get() = megaCorp.key
-private val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
+private val MEGA_CORP_KEY get() = megaCorp.keyPair
+private val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
private val MINI_CORP get() = miniCorp.party
-private val MINI_CORP_KEY get() = miniCorp.key
+private val MINI_CORP_KEY get() = miniCorp.keyPair
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
return when (irsSelect) {
1 -> {
@@ -236,7 +238,7 @@ class IRSTests {
private val miniCorpServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY)
private val notaryServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
private val ledgerServices
- get() = MockServices(rigorousMock().also {
+ get() = MockServices(emptyList(), rigorousMock().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ORACLE_PUBKEY)
}, MEGA_CORP.name)
diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt
index 02fc5ca84d..df5bcb95a8 100644
--- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt
+++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt
@@ -44,7 +44,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>())
override fun startMainSimulation(): CompletableFuture {
- om = JacksonSupport.createInMemoryMapper(makeTestIdentityService((banks + regulators + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }))
+ om = JacksonSupport.createInMemoryMapper(makeTestIdentityService(*(banks + regulators + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }.toTypedArray()))
registerFinanceJSONMappers(om)
return startIRSDealBetween(0, 1).thenCompose {
diff --git a/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt
index 6da9d13091..424071d5b3 100644
--- a/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt
+++ b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt
@@ -1,7 +1,7 @@
package net.corda.netmap.simulation
import net.corda.core.utilities.getOrThrow
-import net.corda.testing.LogHelper
+import net.corda.testing.internal.LogHelper
import org.junit.Test
class IRSSimulationTest {
diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/DummyIssueAndMove.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/DummyIssueAndMove.kt
index c4c5bde481..c3337e61e8 100644
--- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/DummyIssueAndMove.kt
+++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/DummyIssueAndMove.kt
@@ -24,7 +24,7 @@ class DummyIssueAndMove(private val notary: Party, private val counterpartyNode:
data class DummyCommand(val dummy: Int = 0) : CommandData
- data class State(override val participants: List, private val discriminator: Int) : ContractState
+ data class State(override val participants: List, val discriminator: Int) : ContractState
@Suspendable
override fun call(): SignedTransaction {
diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt
index 9dde805ec6..7c2a65c031 100644
--- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt
+++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt
@@ -18,7 +18,7 @@ import net.corda.finance.contracts.getCashBalance
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.vault.VaultSchemaV1
-import net.corda.testing.contracts.VaultFiller.Companion.calculateRandomlySizedAmounts
+import net.corda.testing.internal.vault.VaultFiller.Companion.calculateRandomlySizedAmounts
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
import net.corda.traderdemo.flow.SellerFlow
import java.util.*
diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt
index 7f73c6f306..bf36525538 100644
--- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt
+++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt
@@ -9,6 +9,7 @@ import net.corda.core.transactions.WireTransaction
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockTransactionStorage
import org.junit.Rule
@@ -46,13 +47,13 @@ class TransactionGraphSearchTests {
val notaryServices = MockServices(listOf("net.corda.testing.contracts"), rigorousMock(), dummyNotary)
val originBuilder = TransactionBuilder(dummyNotary.party)
.addOutputState(DummyState(random31BitValue()), DummyContract.PROGRAM_ID)
- .addCommand(command, megaCorp.pubkey)
+ .addCommand(command, megaCorp.publicKey)
val originPtx = megaCorpServices.signInitialTransaction(originBuilder)
val originTx = notaryServices.addSignature(originPtx)
val inputBuilder = TransactionBuilder(dummyNotary.party)
.addInputState(originTx.tx.outRef(0))
- .addCommand(dummyCommand(megaCorp.pubkey))
+ .addCommand(dummyCommand(megaCorp.publicKey))
val inputPtx = megaCorpServices.signInitialTransaction(inputBuilder)
val inputTx = megaCorpServices.addSignature(inputPtx)
diff --git a/settings.gradle b/settings.gradle
index e16e3ca0aa..6cbb77c684 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -37,6 +37,7 @@ include 'tools:explorer:capsule'
include 'tools:demobench'
include 'tools:loadtest'
include 'tools:graphs'
+include 'tools:bootstrapper'
include 'example-code'
project(':example-code').projectDir = file("$settingsDir/docs/source/example-code")
include 'samples:attachment-demo'
diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt
index 8c3636ffc1..4212902a73 100644
--- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt
+++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt
@@ -288,11 +288,13 @@ class FlowStackSnapshotTest {
@Test
fun `flowStackSnapshot object is serializable`() {
- val mockNet = MockNetwork(threadPerNode = true)
+ val mockNet = MockNetwork(emptyList(), threadPerNode = true)
val node = mockNet.createPartyNode()
node.registerInitiatedFlow(DummyFlow::class.java)
node.services.startFlow(FlowStackSnapshotSerializationTestingFlow()).resultFuture.get()
val thrown = try {
+ // Due to the [MockNetwork] implementation, the easiest way to trigger object serialization process is at
+ // the network stopping stage.
mockNet.stopNodes()
null
} catch (exception: Exception) {
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt
index 6135760dbb..cf52920202 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt
@@ -45,8 +45,8 @@ import kotlin.concurrent.thread
* a service is addressed.
*/
@ThreadSafe
-class InMemoryMessagingNetwork(
- val sendManuallyPumped: Boolean,
+class InMemoryMessagingNetwork internal constructor(
+ private val sendManuallyPumped: Boolean,
private val servicePeerAllocationStrategy: ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(),
private val messagesInFlight: ReusableLatch = ReusableLatch()
) : SingletonSerializeAsToken() {
@@ -94,7 +94,7 @@ class InMemoryMessagingNetwork(
/**
* Creates a node at the given address: useful if you want to recreate a node to simulate a restart.
*
- * @param manuallyPumped if set to true, then you are expected to call the [InMemoryMessaging.pump] method on the [InMemoryMessaging]
+ * @param manuallyPumped if set to true, then you are expected to call [InMemoryMessaging.pumpReceive]
* in order to cause the delivery of a single message, which will occur on the thread of the caller. If set to false
* then this class will set up a background thread to deliver messages asynchronously, if the handler specifies no
* executor.
@@ -153,9 +153,6 @@ class InMemoryMessagingNetwork(
}
}
-
- val everyoneOnline: AllPossibleRecipients = object : AllPossibleRecipients {}
-
fun stop() {
val nodes = synchronized(this) {
counter = -1
@@ -226,7 +223,7 @@ class InMemoryMessagingNetwork(
return transfer
}
- fun pumpSendInternal(transfer: MessageTransfer) {
+ private fun pumpSendInternal(transfer: MessageTransfer) {
when (transfer.recipients) {
is PeerHandle -> getQueueForPeerHandle(transfer.recipients).add(transfer)
is ServiceHandle -> {
@@ -271,7 +268,8 @@ class InMemoryMessagingNetwork(
private val peerHandle: PeerHandle,
private val executor: AffinityExecutor,
private val database: CordaPersistence) : SingletonSerializeAsToken(), MessagingService {
- inner class Handler(val topicSession: String, val callback: MessageHandler) : MessageHandlerRegistration
+ privateinner class Handler(val topicSession: String,
+ val callback: MessageHandler) : MessageHandlerRegistration
@Volatile
private var running = true
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt
deleted file mode 100644
index 654b99bb12..0000000000
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package net.corda.testing.node
-
-import net.corda.core.concurrent.CordaFuture
-import net.corda.core.crypto.entropyToKeyPair
-import net.corda.core.identity.CordaX500Name
-import net.corda.core.internal.concurrent.doneFuture
-import net.corda.core.node.NodeInfo
-import net.corda.core.node.services.NetworkMapCache
-import net.corda.core.utilities.NetworkHostAndPort
-import net.corda.node.services.config.NodeConfiguration
-import net.corda.node.services.network.PersistentNetworkMapCache
-import net.corda.nodeapi.internal.persistence.CordaPersistence
-import net.corda.testing.getTestPartyAndCertificate
-import rx.Observable
-import rx.subjects.PublishSubject
-import java.math.BigInteger
-
-/**
- * Network map cache with no backing map service.
- */
-class MockNetworkMapCache(database: CordaPersistence) : PersistentNetworkMapCache(database, emptyList()) {
- private companion object {
- val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public)
- val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public)
- val BANK_C_ADDR = NetworkHostAndPort("bankC", 8080)
- val BANK_D_ADDR = NetworkHostAndPort("bankD", 8080)
- }
-
- override val changed: Observable = PublishSubject.create()
- override val nodeReady: CordaFuture get() = doneFuture(null)
-
- init {
- val mockNodeA = NodeInfo(listOf(BANK_C_ADDR), listOf(BANK_C), 1, serial = 1L)
- val mockNodeB = NodeInfo(listOf(BANK_D_ADDR), listOf(BANK_D), 1, serial = 1L)
- addNode(mockNodeA)
- addNode(mockNodeB)
- }
-}
-
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt
index dfda6b82fe..482ea0fa47 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt
@@ -22,14 +22,14 @@ import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
+import net.corda.core.utilities.seconds
+import net.corda.node.VersionInfo
import net.corda.node.internal.AbstractNode
import net.corda.node.internal.StartedNode
import net.corda.node.internal.cordapp.CordappLoader
-import net.corda.node.services.api.SchemaService
-import net.corda.node.services.config.BFTSMaRtConfiguration
-import net.corda.node.services.config.NodeConfiguration
-import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.api.IdentityServiceInternal
+import net.corda.node.services.api.SchemaService
+import net.corda.node.services.config.*
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
@@ -37,12 +37,15 @@ import net.corda.node.services.transactions.BFTSMaRt
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
-import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.ServiceIdentityGenerator
-import net.corda.nodeapi.internal.NotaryInfo
-import net.corda.nodeapi.internal.NetworkParametersCopier
-import net.corda.testing.common.internal.testNetworkParameters
+import net.corda.nodeapi.internal.config.User
+import net.corda.nodeapi.internal.network.NetworkParametersCopier
+import net.corda.nodeapi.internal.network.NotaryInfo
+import net.corda.nodeapi.internal.persistence.CordaPersistence
+import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.DUMMY_NOTARY_NAME
+import net.corda.testing.common.internal.testNetworkParameters
+import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.testThreadFactory
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
@@ -71,13 +74,13 @@ data class MockNetworkParameters(
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(),
val defaultFactory: (MockNodeArgs) -> MockNetwork.MockNode = MockNetwork::MockNode,
val initialiseSerialization: Boolean = true,
- val cordappPackages: List = emptyList()) {
+ val notarySpecs: List = listOf(MockNetwork.NotarySpec(DUMMY_NOTARY_NAME))) {
fun setNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean) = copy(networkSendManuallyPumped = networkSendManuallyPumped)
fun setThreadPerNode(threadPerNode: Boolean) = copy(threadPerNode = threadPerNode)
fun setServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy) = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy)
fun setDefaultFactory(defaultFactory: (MockNodeArgs) -> MockNetwork.MockNode) = copy(defaultFactory = defaultFactory)
fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization)
- fun setCordappPackages(cordappPackages: List) = copy(cordappPackages = cordappPackages)
+ fun setNotarySpecs(notarySpecs: List) = copy(notarySpecs = notarySpecs)
}
/**
@@ -90,7 +93,8 @@ data class MockNodeParameters(
val forcedID: Int? = null,
val legalName: CordaX500Name? = null,
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
- val configOverrides: (NodeConfiguration) -> Any? = {}) {
+ val configOverrides: (NodeConfiguration) -> Any? = {},
+ val version: VersionInfo = MOCK_VERSION_INFO) {
fun setForcedID(forcedID: Int?) = copy(forcedID = forcedID)
fun setLegalName(legalName: CordaX500Name?) = copy(legalName = legalName)
fun setEntropyRoot(entropyRoot: BigInteger) = copy(entropyRoot = entropyRoot)
@@ -101,7 +105,8 @@ data class MockNodeArgs(
val config: NodeConfiguration,
val network: MockNetwork,
val id: Int,
- val entropyRoot: BigInteger
+ val entropyRoot: BigInteger,
+ val version: VersionInfo = MOCK_VERSION_INFO
)
/**
@@ -120,16 +125,17 @@ data class MockNodeArgs(
* By default a single notary node is automatically started, which forms part of the network parameters for all the nodes.
* This node is available by calling [defaultNotaryNode].
*/
-class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(),
+class MockNetwork(private val cordappPackages: List,
+ defaultParameters: MockNetworkParameters = MockNetworkParameters(),
private val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
private val threadPerNode: Boolean = defaultParameters.threadPerNode,
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
private val defaultFactory: (MockNodeArgs) -> MockNode = defaultParameters.defaultFactory,
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
- private val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY_NAME)),
- private val cordappPackages: List = defaultParameters.cordappPackages) {
+ private val notarySpecs: List = defaultParameters.notarySpecs) {
/** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */
- constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters)
+ @JvmOverloads
+ constructor(cordappPackages: List, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters)
init {
// Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS.
@@ -147,9 +153,13 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
private val networkId = random63BitValue()
private val networkParameters: NetworkParametersCopier
private val _nodes = mutableListOf()
- private val serializationEnv = setGlobalSerialization(initialiseSerialization)
+ private val serializationEnv = try {
+ setGlobalSerialization(initialiseSerialization)
+ } catch (e: IllegalStateException) {
+ throw IllegalStateException("Using more than one MockNetwork simultaneously is not supported.", e)
+ }
private val sharedUserCount = AtomicInteger(0)
- /** A read only view of the current set of executing nodes. */
+ /** A read only view of the current set of nodes. */
val nodes: List get() = _nodes
/**
@@ -220,12 +230,12 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
}
private fun generateNotaryIdentities(): List {
- return notarySpecs.mapIndexed { index, spec ->
+ return notarySpecs.mapIndexed { index, (name, validating) ->
val identity = ServiceIdentityGenerator.generateToDisk(
dirs = listOf(baseDirectory(nextNodeId + index)),
- serviceName = spec.name,
+ serviceName = name,
serviceId = "identity")
- NotaryInfo(identity, spec.validating)
+ NotaryInfo(identity, validating)
}
}
@@ -240,7 +250,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
open class MockNode(args: MockNodeArgs) : AbstractNode(
args.config,
TestClock(Clock.systemUTC()),
- MOCK_VERSION_INFO,
+ args.version,
CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages),
args.network.busyLatch
) {
@@ -385,14 +395,14 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
private fun