From 2f610c2361eaacb34d74d09481364da347e24020 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Wed, 13 Dec 2017 10:46:30 +0000 Subject: [PATCH 01/44] add noise (#2237) --- .../kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index fb92e5b085..cf716e1bf2 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -10,6 +10,7 @@ import net.corda.cordform.CordformContext import net.corda.cordform.CordformNode import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf +import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox import net.corda.core.internal.concurrent.* @@ -893,7 +894,8 @@ fun internalDriver( } fun getTimestampAsDirectoryName(): String { - return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC).format(Instant.now()) + // Add a random number in case 2 tests are started in the same instant. + return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC).format(Instant.now()) + random63BitValue() } fun writeConfig(path: Path, filename: String, config: Config) { From d1ea881aef4d527b195f6ccc23acf500d616e3c7 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Wed, 13 Dec 2017 16:18:42 +0000 Subject: [PATCH 02/44] Inline testNodeConfiguration. (#2238) --- .../events/NodeSchedulerServiceTest.kt | 7 ++-- .../messaging/ArtemisMessagingTests.kt | 17 ++++++--- .../NetworkRegistrationHelperTest.kt | 10 ++++-- .../kotlin/net/corda/testing/node/MockNode.kt | 34 ++++++++++++++---- .../net/corda/testing/node/NodeTestUtils.kt | 35 ------------------- 5 files changed, 54 insertions(+), 49 deletions(-) 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 ed6c808619..7908665021 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 @@ -30,6 +30,7 @@ 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.config.NodeConfiguration import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.* @@ -42,7 +43,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 @@ -97,7 +97,10 @@ 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 { 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 96909bb902..628bef9087 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.utilities.NetworkHostAndPort @@ -12,12 +14,12 @@ 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.node.services.config.CertChainPolicyConfig import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.* 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 @@ -61,9 +63,16 @@ 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 + } LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock()) 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..94f2c181ff 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 @@ -15,7 +15,6 @@ 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 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/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 4871faafbd..96d3683a95 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,13 @@ 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.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.config.* import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.messaging.MessagingService import net.corda.node.services.transactions.BFTNonValidatingNotaryService @@ -41,11 +40,14 @@ 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.nodeapi.internal.config.User +import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.DUMMY_NOTARY_NAME import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.rigorousMock import net.corda.testing.setGlobalSerialization import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils @@ -384,9 +386,9 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete private fun createNodeImpl(parameters: MockNodeParameters, nodeFactory: (MockNodeArgs) -> N, start: Boolean): N { val id = parameters.forcedID ?: nextNodeId++ - val config = testNodeConfiguration( - baseDirectory = baseDirectory(id).createDirectories(), - myLegalName = parameters.legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { + val config = mockNodeConfiguration().also { + doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory + doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties parameters.configOverrides(it) } @@ -472,3 +474,23 @@ open class MessagingServiceSpy(val messagingService: MessagingService) : Messagi fun StartedNode.setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { internals.setMessagingServiceSpy(messagingServiceSpy) } + +private fun mockNodeConfiguration(): NodeConfiguration { + abstract class AbstractNodeConfiguration : NodeConfiguration + return rigorousMock().also { + doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn("trustpass").whenever(it).trustStorePassword + doReturn(emptyList()).whenever(it).rpcUsers + doReturn(null).whenever(it).notary + doReturn(DatabaseConfig()).whenever(it).database + doReturn("").whenever(it).emailAddress + doReturn("").whenever(it).exportJMXto + doReturn(true).whenever(it).devMode + doReturn(null).whenever(it).compatibilityZoneURL + doReturn(emptyList()).whenever(it).certificateChainCheckPolicies + doReturn(VerifierType.InMemory).whenever(it).verifierType + doReturn(5).whenever(it).messageRedeliveryDelaySeconds + doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec + doReturn(null).whenever(it).devModeOptions + } +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index 6c66284a2e..9e3e9c7329 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -2,8 +2,6 @@ package net.corda.testing.node -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever import net.corda.core.context.Actor import net.corda.core.context.AuthServiceId import net.corda.core.context.InvocationContext @@ -15,16 +13,8 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.seconds import net.corda.node.services.api.StartedNodeServices -import net.corda.node.services.config.CertChainPolicyConfig -import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.VerifierType -import net.corda.nodeapi.internal.config.User import net.corda.testing.* -import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import java.nio.file.Path /** * Creates and tests a ledger built by the passed in dsl. @@ -48,31 +38,6 @@ fun ServiceHub.transaction( dsl(TransactionDSL(TestTransactionDSLInterpreter(interpreter, TransactionBuilder(notary)), notary)) } -fun testNodeConfiguration( - baseDirectory: Path, - myLegalName: CordaX500Name): NodeConfiguration { - abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters. - return rigorousMock().also { - doReturn(baseDirectory).whenever(it).baseDirectory - doReturn(myLegalName).whenever(it).myLegalName - doReturn("cordacadevpass").whenever(it).keyStorePassword - doReturn("trustpass").whenever(it).trustStorePassword - doReturn(emptyList()).whenever(it).rpcUsers - doReturn(null).whenever(it).notary - doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties - doReturn(DatabaseConfig()).whenever(it).database - doReturn("").whenever(it).emailAddress - doReturn("").whenever(it).exportJMXto - doReturn(true).whenever(it).devMode - doReturn(null).whenever(it).compatibilityZoneURL - doReturn(emptyList()).whenever(it).certificateChainCheckPolicies - doReturn(VerifierType.InMemory).whenever(it).verifierType - doReturn(5).whenever(it).messageRedeliveryDelaySeconds - doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec - doReturn(null).whenever(it).devModeOptions - } -} - fun testActor(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = Actor(Actor.Id("Only For Testing"), AuthServiceId("TEST"), owningLegalIdentity) fun testContext(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = InvocationContext.rpc(testActor(owningLegalIdentity)) From 929341e7ee921f73e8c36916ef1347f766a1e15e Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 13 Dec 2017 16:22:40 +0000 Subject: [PATCH 03/44] Updates tutorials (general fixes, link to solutions repos) * Updates tutorial to make imports to be added clearer, and to reflect new repo structure. * Adds links to the solution repos for tut 1. * Further fixes based on dry-run. --- docs/source/example-code/build.gradle | 1 + .../template}/TemplateContract.java | 2 +- .../java/tutorial/helloworld/IOUFlow.java | 10 +++++--- .../java/tutorial/helloworld/IOUState.java | 10 +++++--- .../java/tutorial/twoparty/IOUContract.java | 13 +++++++--- .../docs/java/tutorial/twoparty/IOUFlow.java | 5 ++-- .../tutorial/twoparty/IOUFlowResponder.java | 4 +-- .../corda/docs/tutorial/helloworld/flow.kt | 7 ++++-- .../corda/docs/tutorial/helloworld/state.kt | 5 +++- .../corda/docs/tutorial/twoparty/contract.kt | 12 ++++++--- .../net/corda/docs/tutorial/twoparty/flow.kt | 17 +++++++++---- .../docs/tutorial/twoparty/flowResponder.kt | 9 ++++--- docs/source/hello-world-flow.rst | 8 +++--- docs/source/hello-world-running.rst | 25 +++++++++++-------- docs/source/hello-world-state.rst | 3 +-- docs/source/hello-world-template.rst | 21 ++++++++-------- docs/source/tut-two-party-contract.rst | 2 +- docs/source/tut-two-party-flow.rst | 10 ++++++-- 18 files changed, 102 insertions(+), 62 deletions(-) rename docs/source/example-code/src/main/java/{net/corda/docs/java/tutorial/helloworld => com/template}/TemplateContract.java (93%) diff --git a/docs/source/example-code/build.gradle b/docs/source/example-code/build.gradle index 6cad970f10..9757d433c7 100644 --- a/docs/source/example-code/build.gradle +++ b/docs/source/example-code/build.gradle @@ -33,6 +33,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/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/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/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 `. From 5720697b0df6f00ca8520847c7da81eaac3af698 Mon Sep 17 00:00:00 2001 From: igor nitto Date: Wed, 13 Dec 2017 17:09:09 +0000 Subject: [PATCH 04/44] [CORDA-827] Improved unit tests coverage and documentation (#2229) * Extend unit test on RPCSecurityManager * Fix corner cases in permission parsing and bug in tryAuthenticate * Rework docsite page * Add missing ChangeLog entry --- .../corda/client/rpc/RPCPermissionsTests.kt | 3 + docs/source/changelog.rst | 2 + docs/source/clientrpc.rst | 135 +++++++++++++++- docs/source/corda-configuration-file.rst | 3 + docs/source/corda-nodes-index.rst | 1 - docs/source/node-auth-config.rst | 136 ---------------- .../internal/security/RPCSecurityManager.kt | 2 +- .../security/RPCSecurityManagerImpl.kt | 27 ++-- .../node/services/config/NodeConfiguration.kt | 2 +- .../node/services/RPCSecurityManagerTest.kt | 153 +++++++++++++++++- 10 files changed, 309 insertions(+), 155 deletions(-) delete mode 100644 docs/source/node-auth-config.rst 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/docs/source/changelog.rst b/docs/source/changelog.rst index 01270b15a3..13f5f3d440 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,8 @@ from the previous milestone release. UNRELEASED ---------- +* Support for external user credentials data source and password encryption [CORDA-827]. + * Exporting additional JMX metrics (artemis, hibernate statistics) and loading Jolokia agent at JVM startup when using DriverDSL and/or cordformation node runner. diff --git a/docs/source/clientrpc.rst b/docs/source/clientrpc.rst index 14d29ba9b9..7f8322bbfb 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,136 @@ 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 +----------------------- + +Hard coding user accounts in the ``rpcUsers`` field provides a quick way of allowing node's RPC to be accessed by a fixed +set of authenticated users but has some obvious shortcomings. To support use cases aiming for higher security and flexibility, +Corda RPC security system offers additional features such as: + + * Fetching users credentials and permissions from external data source (e.g.: a remote RDBMS), with optional caching + in node memory. In particular, this allows user credentials and permissions externally to be updated externally without + requiring node's restart. + * 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 and defined by the `Apache Shiro framework `_ + +These features are controlled by a set of options nested in the ``security`` field of a node configuration. + +.. warning:: The ``rpcUsers`` field is now deprecated in favour of the set the ``security`` config structure. A node + configuration specifying both ``rpcUsers`` and ``security`` fields will trigger an exception during node startup. + +The following example configuration points the node to a remote RDBMS storing hash-encrypted passwords and enable caching +of user data in node's memory: + +.. container:: codeset + + .. sourcecode:: groovy + + security = { + authService = { + dataSource = { + type = "DB", + passwordEncryption = "SHIRO_1_CRYPT", + connection = { + jdbcUrl = "" + username = "" + password = "" + driverClassName = "" + } + } + options = { + cache = { + expireAfterSecs = 120 + maxEntries = 10000 + } + } + } + } + +Moreover, for practical reasons, we can still have an hard-coded static list of users embedded in the ``security`` +structure like in the old ``rpcUsers`` format, by specifying a ``dataSource`` of ``INMEMORY`` type: + +.. container:: codeset + + .. sourcecode:: groovy + + security = { + authService = { + dataSource = { + type = "INMEMORY", + users = [ + { + username = "", + password = "", + permissions = ["", "", ...] + }, + ... + ] + } + } + } + +Authentication/authorisation data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``dataSource`` field defines the data provider supplying credentials and permissions for users. It currently exists +in two forms, identified by the subfield ``type``: + + :INMEMORY: A list of user credentials and permissions hard-coded in configuration in the ``users`` field (see example 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 + + Unlike the ``INMEMORY`` case, in the user database permissions are assigned to *roles* rather than individual users. + + .. note:: There is no prescription on the SQL type of the columns (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 production environment where security is critical. Passwords are assumed +to be in plain format by default, unless a different format is specified ny the ``passwordEncryption`` field, like: + +.. container:: codeset + + .. sourcecode:: groovy + + passwordEncryption = SHIRO_1_CRYPT + +``SHIRO_1_CRYPT`` identifies the `Apache Shiro fully reversible +Modular Crypt Format `_, +currently the only non-plain password hash-encryption format supported by Corda. Passwords can be hash-encrypted in this +format using the `Apache Shiro Hasher command line tool `_. + +Caching users data +^^^^^^^^^^^^^^^^^^ + +A cache layer on top of the external data source of users credentials and permissions can significantly benefit +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`` +with entries expiring and refreshed after ``expireAfterSecs`` number of 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 b8361203e4..566e3d5e64 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -90,6 +90,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 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/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/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 7472c067bc..7d47ea60cb 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 @@ -196,7 +196,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/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 From f9f476b4f3ef9298343e802ee94e9348e1bb2e74 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 13 Dec 2017 17:41:34 +0000 Subject: [PATCH 05/44] Moves upgrade notes to be more visible. --- docs/source/building-a-cordapp-index.rst | 1 + docs/source/release-process-index.rst | 1 - docs/source/upgrade-notes.rst | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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/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 `. From 1b49c50c8e38e9a1c60c71eb2b8b1fd1823a5675 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 13 Dec 2017 17:46:31 +0000 Subject: [PATCH 06/44] Clarifies that only the first two tutorials are related and form a sequence. --- docs/source/tutorials-index.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 From 7cfe7f2a7842bf327be8b7881e26bb0963378530 Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Wed, 13 Dec 2017 18:21:00 +0000 Subject: [PATCH 07/44] Windows space in path escape (#2246) --- .../kotlin/net/corda/plugins/NodeRunner.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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. From e781d816a8bcf9c1504cb305a2822a3dabb84ef2 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 12 Dec 2017 14:21:19 +0000 Subject: [PATCH 08/44] Removed remaining uses of KRYO_P2P_CONTEXT --- build.gradle | 7 ---- .../internal/NetworkParametersGenerator.kt | 4 +-- .../corda/testing/SerializationTestHelpers.kt | 35 +++++++++---------- .../kotlin/net/corda/demobench/DemoBench.kt | 5 ++- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index b9d73bfdf5..281288e9f8 100644 --- a/build.gradle +++ b/build.gradle @@ -146,13 +146,6 @@ allprojects { tasks.withType(Test) { // Prevent the project from creating temporary files outside of the build directory. systemProperties['java.io.tmpdir'] = buildDir - - // Ensures that "net.corda.testing.amqp.enable" is passed correctly from Gradle command line - // down to JVM executing unit test. It looks like we are running unit tests in the forked mode - // and all the "-D" parameters passed to Gradle not making it to unit test level - // TODO: Remove once we fully switched to AMQP - final AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable" - systemProperty(AMQP_ENABLE_PROP_NAME, System.getProperty(AMQP_ENABLE_PROP_NAME)) } group 'net.corda' 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 index 185e8bda8f..59ce2eb9d4 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt @@ -15,7 +15,6 @@ 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 @@ -91,13 +90,12 @@ class NetworkParametersGenerator { // 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) + AMQP_P2P_CONTEXT) ) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 026c76e56d..608db48d82 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -95,22 +95,21 @@ fun setGlobalSerialization(armed: Boolean): GlobalSerializationEnvironment { } } -private fun createTestSerializationEnv(label: String) = object : SerializationEnvironmentImpl( - SerializationFactoryImpl().apply { - registerScheme(KryoClientSerializationScheme()) - registerScheme(KryoServerSerializationScheme()) - registerScheme(AMQPClientSerializationScheme(emptyList())) - registerScheme(AMQPServerSerializationScheme(emptyList())) - }, - AMQP_P2P_CONTEXT, - KRYO_RPC_SERVER_CONTEXT, - KRYO_RPC_CLIENT_CONTEXT, - AMQP_STORAGE_CONTEXT, - KRYO_CHECKPOINT_CONTEXT) { - override fun toString() = "testSerializationEnv($label)" +private fun createTestSerializationEnv(label: String): SerializationEnvironmentImpl { + val factory = SerializationFactoryImpl().apply { + registerScheme(KryoClientSerializationScheme()) + registerScheme(KryoServerSerializationScheme()) + registerScheme(AMQPClientSerializationScheme(emptyList())) + registerScheme(AMQPServerSerializationScheme(emptyList())) + } + return object : SerializationEnvironmentImpl( + factory, + AMQP_P2P_CONTEXT, + KRYO_RPC_SERVER_CONTEXT, + KRYO_RPC_CLIENT_CONTEXT, + AMQP_STORAGE_CONTEXT, + KRYO_CHECKPOINT_CONTEXT + ) { + override fun toString() = "testSerializationEnv($label)" + } } - -private const val AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable" - -// TODO: Remove usages of this function when we fully switched to AMQP -private fun isAmqpEnabled(): Boolean = java.lang.Boolean.getBoolean(AMQP_ENABLE_PROP_NAME) diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt index 1dc8e11f25..09233c0a02 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt @@ -5,7 +5,7 @@ import net.corda.client.rpc.internal.KryoClientSerializationScheme import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.demobench.views.DemoBenchView -import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT +import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import tornadofx.* @@ -57,13 +57,12 @@ class DemoBench : App(DemoBenchView::class) { } private fun initialiseSerialization() { - val context = KRYO_P2P_CONTEXT nodeSerializationEnv = SerializationEnvironmentImpl( SerializationFactoryImpl().apply { registerScheme(KryoClientSerializationScheme()) registerScheme(AMQPClientSerializationScheme()) }, - context) + AMQP_P2P_CONTEXT) } } From d5f8258bd184c09368c4601fc7a32346039e69f5 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 7 Dec 2017 10:41:20 +0000 Subject: [PATCH 09/44] Updated the changelog to incorporate the new network map design as one story. --- docs/source/changelog.rst | 71 ++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 13f5f3d440..5951fdfbb3 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -8,6 +8,48 @@ UNRELEASED ---------- * Support for external user credentials data source and password encryption [CORDA-827]. + +* 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 sufffers 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. + * Exporting additional JMX metrics (artemis, hibernate statistics) and loading Jolokia agent at JVM startup when using DriverDSL and/or cordformation node runner. @@ -38,24 +80,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. @@ -72,8 +102,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. @@ -89,18 +119,13 @@ 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. - -* ``database.transactionIsolationLevel`` values now follow the ``java.sql.Connection`` int constants but without the - "TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc. +* 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. .. _changelog_v1: From 4ad7b2325e4046422894e9cf3f44d5081aa957d1 Mon Sep 17 00:00:00 2001 From: Tommy Lillehagen Date: Thu, 14 Dec 2017 09:43:46 +0000 Subject: [PATCH 10/44] ENT-1249 - Downgrade to Artemis 2.2 to protect against rouge connections causing OOM --- build.gradle | 2 +- .../kotlin/net/corda/testing/node/internal/RPCDriver.kt | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 281288e9f8..2cc2237b9e 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { ext.capsule_version = '1.0.1' ext.asm_version = '0.5.3' - ext.artemis_version = '2.4.0' + 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/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index f851d7fb8d..21f0675273 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -46,7 +46,7 @@ import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl 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.protocol.RemotingConnection +import org.apache.activemq.artemis.spi.core.remoting.Connection import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3 import java.lang.reflect.Method import java.nio.file.Path @@ -131,13 +131,14 @@ fun rpcDriver( } private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityManager3 { + override fun validateUser(user: String?, password: String?) = isValid(user, password) override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?) = isValid(user, password) - override fun validateUser(user: String?, password: String?, remotingConnection: RemotingConnection?): String? { + override fun validateUser(user: String?, password: String?, remotingConnection: Connection?): String? { return validate(user, password) } - override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?, address: String?, connection: RemotingConnection?): String? { + override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?, address: String?, connection: Connection?): String? { return validate(user, password) } From f3253b46440ea7ed6b8ff1fbd0102b73f1872039 Mon Sep 17 00:00:00 2001 From: Michal Kit Date: Fri, 17 Nov 2017 14:48:36 +0000 Subject: [PATCH 11/44] Adding comment to test --- .../kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt | 2 ++ 1 file changed, 2 insertions(+) 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..a3c499dc92 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 @@ -293,6 +293,8 @@ class FlowStackSnapshotTest { 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) { From 2319bf396cb1a8ef634d7c6ae4a1dd846b439458 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 14 Dec 2017 11:30:55 +0000 Subject: [PATCH 12/44] Renamed TestIdentity.key to keyPair and pubkey to publicKey (#2249) --- .../client/jackson/JacksonSupportTest.kt | 2 +- .../core/crypto/PartialMerkleTreeTest.kt | 4 +-- .../TransactionSerializationTests.kt | 4 +-- .../TransactionEncumbranceTests.kt | 2 +- .../core/transactions/TransactionTests.kt | 2 +- .../tutorial/testdsl/CommercialPaperTest.java | 36 +++++++++---------- .../docs/tutorial/testdsl/TutorialTestDSL.kt | 4 +-- .../contracts/asset/CashTestsJava.java | 20 +++++------ .../finance/contracts/CommercialPaperTests.kt | 12 +++---- .../finance/contracts/asset/CashTests.kt | 14 ++++---- .../contracts/asset/ObligationTests.kt | 10 +++--- .../internal/crypto/X509UtilitiesTest.kt | 2 +- .../internal/serialization/KryoTests.kt | 2 +- .../amqp/SerializationOutputTests.kt | 4 +-- .../services/network/NodeInfoWatcherTest.kt | 2 +- .../services/vault/VaultQueryJavaTests.java | 12 +++---- .../events/NodeSchedulerServiceTest.kt | 2 +- .../identity/InMemoryIdentityServiceTests.kt | 4 +-- .../PersistentIdentityServiceTests.kt | 4 +-- .../persistence/DBTransactionStorageTests.kt | 2 +- .../persistence/HibernateConfigurationTest.kt | 4 +-- .../services/vault/NodeVaultServiceTest.kt | 4 +-- .../node/services/vault/VaultQueryTests.kt | 12 +++---- .../node/services/vault/VaultWithCashTest.kt | 4 +-- .../corda/irs/api/NodeInterestRatesTest.kt | 2 +- .../kotlin/net/corda/irs/contract/IRSTests.kt | 10 +++--- .../traderdemo/TransactionGraphSearchTests.kt | 4 +-- .../net/corda/testing/node/MockServices.kt | 2 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 14 ++++---- .../corda/testing/contracts/VaultFiller.kt | 4 +-- .../net/corda/loadtest/tests/NotaryTest.kt | 2 +- 31 files changed, 104 insertions(+), 102 deletions(-) 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..d5b311e9f4 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 @@ -24,7 +24,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/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index 3968d8cae3..4449f6a6c3 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -31,9 +31,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 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..1fc305f656 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -25,9 +25,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 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..37af722e3c 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -27,7 +27,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 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..9ef046072b 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt @@ -23,7 +23,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/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..573bef81b3 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 @@ -40,9 +40,9 @@ public class CommercialPaperTest { { IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class); - doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPubkey()); + doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPublicKey()); doReturn(null).when(identityService).partyFromKey(BIG_CORP_PUBKEY); - doReturn(null).when(identityService).partyFromKey(ALICE.getPubkey()); + doReturn(null).when(identityService).partyFromKey(ALICE.getPublicKey()); ledgerServices = new MockServices(identityService, MEGA_CORP.getName()); } @@ -79,7 +79,7 @@ public class CommercialPaperTest { ledger(ledgerServices, DUMMY_NOTARY, 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(); }); @@ -95,7 +95,7 @@ public class CommercialPaperTest { ledger(ledgerServices, DUMMY_NOTARY, 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"); }); @@ -111,7 +111,7 @@ public class CommercialPaperTest { ledger(ledgerServices, DUMMY_NOTARY, 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())); @@ -134,7 +134,7 @@ public class CommercialPaperTest { tw.timeWindow(getTEST_TX_TIME()); return tw.failsWith("output states are issued by a command signer"); }); - tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.timeWindow(getTEST_TX_TIME()); return tx.verifies(); }); @@ -154,7 +154,7 @@ public class CommercialPaperTest { tw.timeWindow(getTEST_TX_TIME()); return tw.failsWith("output states are issued by a command signer"); }); - tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.timeWindow(getTEST_TX_TIME()); return tx.verifies(); }); @@ -176,7 +176,7 @@ 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()); return tx.verifies(); @@ -188,8 +188,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; @@ -212,7 +212,7 @@ 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()); return tx.verifies(); @@ -224,8 +224,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 +234,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(); @@ -258,7 +258,7 @@ 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()); return tx.verifies(); @@ -270,8 +270,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 +281,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/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..45bd12a23f 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 @@ -28,9 +28,9 @@ 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 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..b9daac0541 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 @@ -27,36 +27,36 @@ public class CashTestsJava { 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()); + doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPublicKey()); + doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPublicKey()); transaction(new MockServices(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 +64,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 71ae00d507..dde66f4216 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -46,7 +46,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 +105,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 @@ -260,8 +260,8 @@ 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"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.key) + private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair) + private val issuerServices = MockServices(listOf("net.corda.finance.contracts"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.keyPair) private lateinit var moveTX: SignedTransaction @Test fun `issue move and then redeem`() { 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 760aab4aea..e085602d45 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 @@ -37,27 +37,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 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..1677955b79 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 @@ -45,14 +45,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 +77,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(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) 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/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/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 68bc1a5786..a3af4c26ce 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 @@ -49,9 +49,9 @@ 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 } data class Foo(val bar: String, val pub: Int) 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..ba071c1fa1 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 @@ -49,7 +49,7 @@ class NodeInfoWatcherTest { @Before fun start() { val identityService = makeTestIdentityService() - keyManagementService = MockKeyManagementService(identityService, alice.key) + keyManagementService = MockKeyManagementService(identityService, alice.keyPair) nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler) nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY } 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 b71fc7db16..f6fa7aa18a 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 @@ -72,11 +72,11 @@ public class VaultQueryJavaTests { List cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); IdentityServiceInternal identitySvc = makeTestIdentityService(Arrays.asList(MEGA_CORP.getIdentity(), DUMMY_CASH_ISSUER_INFO.getIdentity(), DUMMY_NOTARY.getIdentity())); Pair databaseAndServices = makeTestDatabaseAndMockServices( - Arrays.asList(MEGA_CORP.getKey(), DUMMY_NOTARY.getKey()), + Arrays.asList(MEGA_CORP.getKeyPair(), DUMMY_NOTARY.getKeyPair()), identitySvc, cordappPackages, MEGA_CORP.getName()); - issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), DUMMY_CASH_ISSUER_INFO, BOC.getKey()); + 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); @@ -466,16 +466,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/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 7908665021..4ae3dc09ad 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 @@ -52,7 +52,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) 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..d7c4fa8bbc 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 @@ -27,10 +27,10 @@ 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 + val BOB_PUBKEY get() = bob.publicKey fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities.toSet(), DEV_TRUST_ROOT) } 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..46dc9b5f50 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 @@ -35,10 +35,10 @@ 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 } private lateinit var database: CordaPersistence diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index e495f12f72..7defd484ab 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 @@ -24,7 +24,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 e323c98851..2d144d4520 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 @@ -61,7 +61,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 @@ -112,7 +112,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/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 0a281f0713..a8f24d73cc 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 @@ -64,8 +64,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 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 c27667d52b..19ccd230bb 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 @@ -64,17 +64,17 @@ 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 @@ -112,7 +112,7 @@ class VaultQueryTests { 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/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index 34ef9e5203..4e1269fc14 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 @@ -49,7 +49,7 @@ 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 } @@ -68,7 +68,7 @@ class VaultWithCashTest { fun setUp() { LogHelper.setLevel(VaultWithCashTest::class) val databaseAndServices = makeTestDatabaseAndMockServices( - listOf(generateKeyPair(), dummyNotary.key), + listOf(generateKeyPair(), dummyNotary.keyPair), makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)), cordappPackages, MEGA_CORP.name) 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..3c8b0192f3 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 @@ -38,7 +38,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..6615d5f7c0 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 @@ -39,14 +39,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 -> { 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..eff4a70136 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 @@ -46,13 +46,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/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index f5926637ac..a49b934afd 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -111,7 +111,7 @@ open class MockServices private constructor( private constructor(cordappLoader: CordappLoader, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, keys: Array) : this(cordappLoader, MockTransactionStorage(), identityService, initialIdentityName, keys) constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, vararg keys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentityName, keys) - constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity.name, arrayOf(initialIdentity.key) + moreKeys) + constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity.name, arrayOf(initialIdentity.keyPair) + moreKeys) constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, vararg keys: KeyPair) : this(emptyList(), identityService, initialIdentityName, *keys) constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name) : this(identityService, initialIdentityName, generateKeyPair()) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 5b58e65086..e0100da05d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -3,6 +3,7 @@ package net.corda.testing +import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.StateRef import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash @@ -32,6 +33,7 @@ import org.mockito.internal.stubbing.answers.ThrowsException import java.lang.reflect.Modifier import java.math.BigInteger import java.nio.file.Files +import java.security.KeyPair import java.security.PublicKey import java.util.* import java.util.concurrent.atomic.AtomicInteger @@ -81,7 +83,7 @@ fun freePort(): Int = freePortCounter.getAndAccumulate(0) { prev, _ -> 30000 + ( */ fun getFreeLocalPorts(hostName: String, numberToAlloc: Int): List { val freePort = freePortCounter.getAndAccumulate(0) { prev, _ -> 30000 + (prev - 30000 + numberToAlloc) % 10000 } - return (freePort..freePort + numberToAlloc - 1).map { NetworkHostAndPort(hostName, it) } + return (freePort until freePort + numberToAlloc).map { NetworkHostAndPort(hostName, it) } } fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration = object : SSLConfiguration { @@ -117,11 +119,11 @@ fun getTestPartyAndCertificate(name: CordaX500Name, publicKey: PublicKey): Party } class TestIdentity @JvmOverloads constructor(val name: CordaX500Name, entropy: Long? = null) { - val key = if (entropy != null) entropyToKeyPair(BigInteger.valueOf(entropy)) else generateKeyPair() - val pubkey get() = key.public!! - val party = Party(name, pubkey) - val identity by lazy { getTestPartyAndCertificate(party) } // Often not needed. - fun ref(vararg bytes: Byte) = party.ref(*bytes) + val keyPair: KeyPair = if (entropy != null) entropyToKeyPair(BigInteger.valueOf(entropy)) else generateKeyPair() + val publicKey: PublicKey get() = keyPair.public + val party: Party = Party(name, publicKey) + val identity: PartyAndCertificate by lazy { getTestPartyAndCertificate(party) } // Often not needed. + fun ref(vararg bytes: Byte): PartyAndReference = party.ref(*bytes) } @Suppress("unused") diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt index e303cba51f..06e981c9b1 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt @@ -80,7 +80,7 @@ class VaultFiller @JvmOverloads constructor( addCommand(dummyCommand()) } val stx = issuerServices.signInitialTransaction(dummyIssue) - return@map services.addSignature(stx, defaultNotary.pubkey) + return@map services.addSignature(stx, defaultNotary.publicKey) } services.recordTransactions(transactions) // Get all the StateAndRefs of all the generated transactions. @@ -101,7 +101,7 @@ class VaultFiller @JvmOverloads constructor( linearTimestamp: Instant = now()): Vault { val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey val me = AnonymousParty(myKey) - val issuerKey = defaultNotary.key + val issuerKey = defaultNotary.keyPair val signatureMetadata = SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(issuerKey.public).schemeNumberID) val transactions: List = (1..numberToCreate).map { // Issue a Linear state diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt index d1d500b7a2..f54970ff69 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt @@ -27,7 +27,7 @@ data class NotariseCommand(val issueTx: SignedTransaction, val moveTx: SignedTra val dummyNotarisationTest = LoadTest( "Notarising dummy transactions", generate = { _, _ -> - val issuerServices = MockServices(makeTestIdentityService(listOf(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity)), megaCorp.name, dummyCashIssuer.key) + val issuerServices = MockServices(makeTestIdentityService(listOf(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity)), megaCorp.name, dummyCashIssuer.keyPair) val generateTx = Generator.pickOne(simpleNodes).flatMap { node -> Generator.int().map { val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice From 0df846148dcee7e1260c5fecccbd854398d58a40 Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Thu, 14 Dec 2017 12:06:44 +0000 Subject: [PATCH 13/44] SignedNetworkMap verification fix (#2255) * SignedNetworkMap verification fix SignedNetworkMap verification should also include cert path validation, which was probably moved away by accident, because docs say about the exception CertPathValidatorException. --- .../main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt | 7 +++++-- .../net/corda/node/services/network/NetworkMapClient.kt | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt index 871b1e514f..9fcd59ea95 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt @@ -7,6 +7,7 @@ import net.corda.core.identity.Party 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 @@ -63,9 +64,11 @@ class SignedNetworkMap(val raw: SerializedBytes, val sig: DigitalSig * @throws CertPathValidatorException if the certificate path is invalid. * @throws SignatureException if the signature is invalid. */ - @Throws(SignatureException::class) - fun verified(): NetworkMap { + @Throws(SignatureException::class, CertPathValidatorException::class) + fun verified(trustedRoot: X509Certificate): NetworkMap { sig.by.publicKey.verify(raw.bytes, sig) + // Assume network map cert is under the default trust root. + X509Utilities.validateCertificateChain(trustedRoot, sig.by, trustedRoot) return raw.deserialize() } } 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..436c56fdaa 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 @@ -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) } From 5b33db93fc1c35d5596eeeb589f120d855761443 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Thu, 14 Dec 2017 12:30:07 +0000 Subject: [PATCH 14/44] Add doc comments on supported TLS schemes and scheme hash functions (#2161) --- .../kotlin/net/corda/core/crypto/Crypto.kt | 21 ++++++++++-------- docs/source/permissioning.rst | 15 ++++++++++++- .../resources/certificate_structure.png | Bin 0 -> 110874 bytes 3 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 docs/source/resources/certificate_structure.png 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/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/resources/certificate_structure.png b/docs/source/resources/certificate_structure.png new file mode 100644 index 0000000000000000000000000000000000000000..6121b968da6c00934cf31c5339a165755f299af2 GIT binary patch literal 110874 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 z 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
L@3GFvD4Rc9!WYa z$tSz}MQhC0_tw^EsRBu~q;Du-btFsYv5yjwJpE(T%n)(3k&Br4aBgrx$@dn7=6HZ; zWP4a5s12tZ-nCq}Z9Uv+utG16#+((cL{{GMGE%83HTz_|7Lsc+}t#I0+0P3|12VRXsj_kH)jJ{J< zoYC{U2CPv z_MChFJI-*NG2F7*@qKHpIp_O6ugu@g=>0N(oXiM0<5Y;ZZaI(8XJ|UW#Y!t$(y9>X zythPZUZfgQeC}*RgqIt%9CYo|p48WAzK8j(Vby{RcRAjfat8w{dF@{O&H z>KBy`CLy2noKNWyD_QL?%z05^J^qTGg5?WG9e$xqxK{{o-C__eZ?z4A>B{W`Z*Wj^ z#FE71862=!rPc(aix%{oAfQzY(~kP;4QSZ}Q1j};aH9VnW3pyzM+dj#rq)ej&;`|y z&bxwgZMA(wo&ixusXJkivlu7G1FjGmSjnV(-x$YK3;DQm{94YAyo?3w9(`aTBbfq?MDFwXVBCzTG9FZC_uD=!DARMo1#So)1+7Xe2^W{Z4w=`l8K6@QEvTb~ zUfssG;>5%slxyl*wvbD_k=!MM!!fKm510hT)wG8mDe5)JFj>^( z^7EdU*`Z8)s3NO$cK@4|$umrkByKcva&qXOwZo)6FeD@z(~vlzvQhw&20CCP-Pzqm zSSy0bMB^*|3`%F48IQ?_e9Jht|FP_wtc`um1UWQ{Cm`6mC!y!8lG4RatS3Y-hIM~+ zC|FC7z27a&d5`F`L5I@gxLs~QXTOHZ0nzk_DV*SP$pZ!^Cg{Jn>3UvBKY4-&oluZT zc0!!N2MWQxbYr&nm`kq)8xIq);!#!78(F8J031 z#^9&1R_p0z_a|v$|F1hS?sU$nmuj0wW^c~IY%#o-(@cXe7FpN5RhE4!{ctU#Gl5OB znlfE4S)D~X0|b^kN{V8uhd8!bhfU)iT(m~2pjV{2-mKXb{oQg&Xt~9V_V-VQHVFpV zQEQJax5V<`H{CNmb|%^>6>F*s(eJ7jw7%;{-Hi`(BvbvOP~-QQYKYP?2SZD&@>i50 zA=dH3RE04KqQ7#=dnuUd*bJ2>O-&Fj3cSzkz;Zk?t)=Hlbs# zVm6`h!BN7gHgh%J*$5LLklJ>%Z3SmUyVl1&RNq|n#3fE~rwA{P;CZ^UF;vA{M4T&{ zH?K8e7d&3L5AB5aX3CK#ggeu%N^RO=( zf1wf{_y@0}H|z@(GCSiDyo_6_rv7VkQZ{CQ6ZnTHd(SkLg0?;IhW$72ETt@CiN1Oi z8uH&KHB|f{T)~-3pYT3E$!%}{j`p+veROH^%lZP?gyC#nq%Wtf#PZ{5XwZLuc4_L% zv0j7qEqT`Z({p$2Cpv#tfx!LWm!yI0jdh$4bHSzFl^lhvzo^iENEc1HRYth17PTV_ zyf9-CQ0`;<@xPDO3KMcFQEP!;J+1}747{}}7XC@L7QL|4eX1ez-}i<8s%b)c z^ycW#57MYGga9o&aGoRj^x$p(y~U)|?>S-|&i$b#XN~0qRhBBC*v8!gGAOA3=U)s& z1w3+X4C{5R2c+uJbk23wWV}U|4%z>_eSG3>XC8suYl<%K5}$pLY}!NiBn8m-KQAB9 z0JfiIlC;EXKB?VY0E$j&U?Z3I1i5 z&Q{^;XQ-LA5N`fn^=$Zl)L^7MsGDFt#fQV0gE4VzK+jMGZjJw3dQdVMHJevC8l$R( za?rySf?naila2qXJbVuI+Ji*Q31W=@e{t`ZJ&HG9gCrgX=o@0#qpTfL|M_a(=UNq? z-djI&0QHppG5&VnD*h}y2eK1ty%&j;pva>n^rHUkTTbyh^QcY_eDf}~12U*4-(*dF z+Hw38zLHrHBShymPOv$?HZpgnr-^%|{gntXkGm~rr30nPT)pgX4iXC2g<_=>mw+9jl zY193{w=f?WZ%r{}RxLE>x;=dNIC=WQP%>4xRUim~M*ZhD-PV;Jr6$lO<*)sx5LepN z5`@eD)JG`uJ*P-uje+D*Xb7As`IR}KD1w0(27OJOjH&y{=^pMAdFwUAJv71(IR%W6@PPb0u;O`dm z-*xYt1Yhj?t7%7$;h7m#C6+&?s)8OfvVyPit% zyVMM1V}^>C&%2Ja$xp=oyEGhE(47mfk>G-pAHOt$e(IP)ly(z_?1X;{&tPV=a zcN1C2%U<;93}-eDS8fk`^O4U_%9UX{Iw4j=7BPv1@4@`SH*zwdsvNYtAnrod;;)-_ zp5Y<-aVyni@mV94Rz(=7qj2ekQJKA`y+yT7+!;NobaFE^>XrXNTihaE(D zmVta*queRd9xxL}&15rp45ug-*Qy@#KD+H=?~FEH))I(WgcABNm6<_kp#06Gos<(Z zV7tFa$0j9CH^4q{V4`N&-m1KM^@8n&+XX(MpCheWJ_UfCfhH$cSq9FR1F^b+uQyCd zJd{_Q2gDeUAAaXM;rRcLBrI}qn_;0hB$#y68|GA5_)`I0n0QoK1)mNWO#N1Q zkzHInFRuw)wVqsH(l%Y!_3u>Aqde5RE)@DOQT$}@b=I?!IU!jAnL7|f@}FB5SIWuB zqzrADwGpF!H7`f>T;S9I(S=9XEYGKao*h+E0+L7Vfy{OAW9afm0pJsQ%&K1#F6NCI z1&2BQ6to@h;Z|CkFlHTgbKjAHOJgvKyVifFr3MZP>)}caQxi(ykqxHK^eneK9BIV& za~o)PQtU2c$1@H)sKy5o@mT#wD*R6vw*S44MJ_u3{Ja(!@CScZPu~*cg)vO9T^4?q z=}CkrhrZlSQy_j2qtDFsWenzMLqEpZX1TTb;RycaY01&S2V2(7bl=~HVm z-7@{)uao)me{TEacsvF6e5ciq6Y&46S-$Y~Y_<3n>TT~wHE0T?{_3lq^LsPAtU7px z7kaPxTAu~o^=SWK@2gJsg0a%T#r$hD@iqgvR#9j=Rj%&ZYr>Gie`w}AKU5M{RrQzcp$Lkt-OrQXPxj0L3h6J09qx``br|p6hB_#(x8<7L>c8-RC$ZIQ zPjNEw8)*U@7k8W91L3!`u|RVmb=5SAU#JZI%vV<{F@mYnubOYWs3-{0MXvw;dlt^(^Q_{whJEV+EI6*DdbY`Kd{u- zIyI3$P9TP7oMl$SHBo{9Q4e2M%L8%k(+;G+;L_^&=%Ys2-h_Yi~)mhTUc1AtEH=55Xq zph`o?93j)W*ZVe9TB=KP)47)Q{nrInnxvGM9b9G@*9aCaFcIbm&ejXI1UOCpC#m9n z$M6+n>^7SwoI@!k<)>WM*a0NNmG?GD)b-OpqV^!pdc3{v&)}eX2w6124r=+2@y{uk zOrf>}11I9gv!w#uU@7nnbG4Uu;g?)hxfO z()9x7#vq9N$L`(6$1Oo2XyeO2?KXfn_w-`?VX8mJ2+19;^Ii`5`clhrsNjKd{iz=I zr@wu9d<-QkRFv5JU$SVaLA*Qz@}D@JZ~x2~fL>y=H6>cq{rDCL0=UKuB2QmnG*l}p zXV%a=w)kR$cadYvhy5rtY9 z1l1N?k&E5#KP<37bIT}&vFN|o<^WIsWNz$L z-AD1Y+1XdB5rYk$o)B+gKCT+v7d*i5%+_z(bD6i1kU;C;gNaQjB8lYht4>cKXySNR z>&eUD1dc94#z%m*;Sw8%&_s^*x9!#&W4xg4OBr*-G25@9Vy@7?N(v32^wPiVRJgTYui|Z~c0bMgGFFX4LF`O;7 z7)vH04Z!Eo8QN87sXuzv&Mm}c%8fZ&?j_!G20bKcP}2ki1#z>n3b~(_(n^D7oj{?R z?mmcC;1J4rar4O1e8iX_f{vE%TsMQa`v!p)5@%zFFSC^JWsN)|FhCsqr*vY?m%cd= zm|X9)2w%_d&8r#qLTeT7s$%*(p#2mTfFwfU$s@G`sdmbYq+pDYl990iVKkNk_;L}V zEHHwC5!YzlRc2}{_yKgG0|Nu&bo?}Huf(8pbX81*_u2A%VS)tzvO>96AMBKyy5rqq zVXLE-6z})StpQf3ARml5gFDN2^%!O673h47$G!@ESss2H*p1ae-+e>h5dn~0IXXED zUyzvy$s5dm@e$&LOlUolmo>jTR4$`mldMd-Tf~8wP@H4xJBHg$f2_7tg=()?{3=m~ z4&k>2+qHVR8L2^g@D~U>=P@6P0#OBM#Vv}BdX)hB#m$3ImVjoFC4)t)@*Z@k;o77i z^=Q!L12t?Kgup^TdJ1&4x$IV+WGbhGf^9&pK<6h9o!i29{CTF{UIRa3BDs%v_?K!K zj7hYe(W~>+clodGL4$Yx^R!u`GUm*@jKkKL2$!7mlZN5bz@b@24Vsi8$Mezx*Z7gk ztF>lx_$hfJ`6sz=kpad#JM1v!mM1dr=b)tPo8pj??Dv-0B1yN)a^Q@>**Q&#{iLGf z!CyCIYS=Nl{$?&Twh%*KGUIDt9>{|G@ipi!CUJD}V8!VG2uNXB*&>W}NHzRq!>FK9 zlW93J7-lt4qxS(_LHBgDzMo7#k!C7R_2<+ek=&F!>azK~K#ikV16t3krfyCIq8xPj zoZOUMn&UJkXoYHFTI~X>DTKLKuG3Td&o{l%ubl1f7gCR=$qK4ijy=qq$!AQz_CWrZ zPs)X7iU$w}Cl$BwKikf$xb3speB_)+Y(SF|Nalz4h*K!x0vqQdsqdxHl zBg{@6T%DMO>yC=7baMI6@94fGXuJ8)H$(|{w$hloV)Y2C%-2b6U^+p}vzWwJbf5HL z-u@2F15bx{_CMs(wNLr;D=Ls>t!9chRPj;4a(($~o2a4EFxO##?fCW~;8OS_7KUq#e;IAMjxZVzy z+lO!8vOpRqV)_7@b3$U`b};#5Yn0uAPbp6mEu8j{pW6x+2QU?N01ah(e;-6!jEK-j zkYq!Y+7ypDsKegDVh5I{hw}3BeuT4N75Q+B^;-?6lQ9`wzz=mu$Op{ktcj% zMLt3z4E9uwN;7yBoR)LZC>nm2Nt&p~2xxkD%2>izJo4ttu^&nAL*z2 z$Wc%SZ08N+pxa%>os`tmErr^TC6qgnXqAT;ZT9Mh+l@a{e&nh?40S!{rBGEwO2@%x za1E1+mtwk#X0Dyjqq_!-H)T)m#fl$b>TizjjI8noQ%-!8xX_qi+fM5_3bFC6q^vX} z@Az;sjjD64T50i2jGa`tk}&Itvp<6D6=ooApw$FUrD@C9d(>^m_|ytQL+oRl<$f9B zo|P`Olrp^^l~mBFpDx7~uB_8u)?%*Khe*4N2D7$s%W;C4V|s>aiPC{IuEcf7&redNb{CrbUnTx zTnJ;zAnAz{b9eF6EVG2w0@5jJJa{hxgmc1f2BC|K-iK;>Ov&4=3JzAn__GdPN0Mn} z**=*5>LrMtu}bK=^EzKJC?7w2W&m6S7}qY^{9Pg_U9s_(@_9>`JfQ`L zRAKBA#Wx82O!T^9D50^}p7%wrQRNR>sVQs^zmN>EEwX8gc+Foo(ks5trMNlXMmM0+ zC{`E$wEQtX$cd$(#|KJD7c1FDN^}HqT!YKi^TCveAglHtB7<=U&GtWi<5DqbblpnL zSLz@<4-@9)bLSI>s_i4QBRC49?&^FWZ7&v_UU8SVV_X_jB^%DuUw6zVaPi87anEUq zjNoO;lN0Vr-LbWiiaK$mL#vH2?B`BpUyJgJ-|SyiSiXo!M+l-m@v*~_oX9BawWYB( z!7F0@TO=N4vHU{e58vP10jkl=#)cx~3TH-@fUS+}1!w*Ft^&enmz9E$Sb#$ML!DR$ zpp@urm%0%_$ubE)AhZwsBVG55Ar=-t>dp%d&RYiDPci*qGP$jyT{~H2rH3!iTJ2zx zOI?CDU~4fvW6gn3B1{}k#am|1L%>u zIGBDzV}F>3q+6a#<@f9+^BQ%juUIYa(@^Qg4X5x; zo1Q^RB40?~v}+VS`*aTUDeIC4yb1R=`v5K#{ex(^*PzQwkAqrMhA)HVMe4oIFQ@Ul z4}3)f>++8-BJ;Q4R6bdr?$n2%;4ko#k1Fa15Sfd8O=C7X>$wrSvnP<`NV(2OB!IN8 zAG+!{<8^eGeJD(1XrilQG9J!Jn|| zY$B$}R>b7jLx-I8m>sWVO4@k`7Mw3Z5$o&kZ{$L{cW~ftCT0pQJJ9|DQ-bF+Snm-2 zx`_r)U93YvUkD!uFUJ?_bxg3IfnJomwO%o9w}dM1&+v2c9vGzIdwdHzQgWzK9vG6X zx5x`qaUq-~&${3f;JpeTToodeD){@v`(9PMnNaZ;A5;$dJ}c z6Rsudb=j~t`!pD1Quy4K)e}#q8UQc1)9^?mhS)|vqit8_{l=#%V@>#(N`t6a9gaV* zJBUm8hRqbaCn_mw^4_S-WsE9G6tV!(1aNtgI!<;6mcVK}Dx$i4S{xGq!)lnm znLEqPf~>6!#jWrq|HFcrlktki6kNqmnl~w$JQ9c>bsf-q`)Jh^sxC8!t$aJ%-@hRO zwN4F&!$n-LxJQgeL3El+bbr*n4EppB+ox?XX)1DSI>Jq-!sVdQF=inuXNbQnO2@6wiq$jt#Q@5?h0`$+EYnLU^qw zsFi*iE-!;H`Za!24Yi&5c6}GHo%BCnbj-aB0=0C2j%gDZ;@J67G#PW zWA*q}QA*}^q1zAc2|Bzr|!U-$yR3YPV!KcenD zJSlF=ev*HC+GFQScDx@6Ey>8vz{=(=XU2v;wWG6hcZm1sCmE5IauQcHwF^D`L?qdt zDL&g#!-OANSt_v$r2~j;#fa#7NYR^Jep=Zq4o2PrtKj>Tl$4hWY9>nCk%;{1Rwdo$ zg=+tX1ZA*fXDp^#j-@kG)E#Y-pUzPkvcYnG{(4jRK@@$#5DSD4HT2|-q&4Hl@L`r9 zhwiTNBn4I5_3$?uPvkL)*tx;MW9f~M=&Or4WQcSxMR!F)4yOK%opGzrG56&2OZF+J zy4p)^7jt4=kz7wOe8hTF?mGtJT6tf$Ev-BiytqbT)IuOiSNm4NGdQE;cjrgKWp01>`HGY;1bep7`>(1}cQ(iAIxKC|iCgQI9Uo(0%ip)CI zC=p=mBRo#)oW;sNN^ON0p|vkm>3W`uTs*aUw_-tX^EE%QhSDq$;h(gdzh9Gg}=J96jU`@)5I<~IqKwSz?hjMpWCZJz2QlNW6WX565;km^7 z-gPZETgOf`Ap5=lrenA$lgC%WSjuTpB*|#)jWE7|Q2^X*nfR|%6lXTI?KLD1?qmdF zmi1?}_7h?0N>-B(<@|tU3d12uKCSwNQ_FMSRM(CQ+P>(d!^mJU$PQz#f=P-PPUOMD zFpBeIqT4w4Akir^iU||c4+@Z>mdmx_xLl5{sJ||eag&inr<7(!8YbM-5o)@P{z@AM zTHe|7t~T=v@+jIKOL-dAmj(IE>%GDuCM#NhbKC3 zXG@JspDrK1sxCKgtTP=iEq$+nA_iOR{u8mh3b`A#K$i6(pGh-R^D<_f|H4ch1Pk%()hgi9tymDIR8Sjjy7ZxrL8)2n-CWH`rk76+j{Kk$DSuy!DxV!YT z0|97D)MI2|c#^LrNQ&LV(3hb=lGZu~_&?pzv(q!MiKPycFCL(eiwZ*tIhz$Z>w4&t z&TWFmAIfPI)?pWVdL_TFn<-Q-N2T#jGLN?V(=qvYrM(N0^+<(SgH_{i>}qr)0OsyW%)7Gi`S0v z?CrVz-8^QS>#sAsA79;K?mpb%Cq#sVTQP0K0!vF=3@$o0^+Fnd9LgWXHV5D;%V29R zFE2+xNvj>VEK0|rdC^d~4@YO|o6Ah6f0jDLE#QbrB^#AS<<;25!}TY1fSKx3E*|iF zU-Ps7%I9A;{QN@F-sDL{f>K9-gInP%O8@AJ>I|wW4+(=dfIZSkKEV z;nthY=8w}_l1~M)RFlx7%MqKWnPD0s0O;psSAMrazyD_heG_iSX9*vwj|j40khS5= z%AlOC6-cAcn?D(PT!f6lIfM9~zg^eUKwxZ1->Mau5e+hl2DU<~$@_@biSj`dvRy10 zuATPYrz2xYmy!h?p3BGF-BI*zqm~ZCBC?2X?`&`=5uWc*`~7G5vtQJK>Pb&m>#(6w z^b?B$`gwzSIcoiXfHs5a+X#8Q_L}Oew+&%Xj=l(M z&x|44wtQr_xL$u98pn@ie z+T>e@hlj=t$58b#FfvNxJiZYYM8ZkNhN=W0^s0tw5?wctfV{D-?~Hd32(W5jICL1e zY3=H*mqfLl3n2OezJ7`6^ERQZ27S#x9uOXZEc9mf<)+U+s=M>)y?2gCRHY_|naOz* zWjV1?tN`qgx;)2KWdfLS5=GQw0`R@JAC*ICvwW%pXds)Xh(I^a!Zcd;!v;w-A;#posz8P4ITyU4SY;xd*-c zkBN!r6H`W@(9V2ODugukvcNYUp7k`pq&Y-K^&}Cod%e_v1ry2gQl!LBi`ZqMdu&W- z64Mtw`t`13m<(7e4^<01VUK1pY9< zp>TO#x`7`v)W-)EU|Cu<4(tON?-^j>D#_&V0DIy-&_sdq0Aj<>9@MK8-ByT>7dLxB z+)U>9u{QZ0JEg;Te`$P5h0;a%bDX~qGETtsK1f*;5D-8RXn%hJpjHIGM$G*bfhz}{ z_HjiQHs%rn74j8&T`)0t1$IgxRp1d5e=^>!MQrH^x=5F+*;smvKINgdCiw7ZnxWZj z=r4z!_frL3r7I@Gsl}an;Gx2l+rdlBZ_n>!o07^3$0>8zze8iFCQq@24IEN7VrWLP zy|Nx;2i;Zv_nYg;uiJ9n#`CtZgW9tjZY3CD-2ipD z0SxR6f0h~#6t^zB#X_V50=){wLYFMHJHXe5Se|@tq~A#W7nKgo9>ce+V44I;65z(K zw8|TR(MgKmBM$+St&0W<)}uTcoEl)TPM%&B6<+5HS(yz`$hyO7so%3*o${}P?D1?uxP>< z?41f&Z{Z?p1HZkst?fLRHzKU!+Yo#Nz$ul0XdS^JSWwD}5cl+ePrC)Y%G?w6;KLsu z?F9|H=-vkB%VK~errn6QL7ehSI29=w7kTJq4`__|q;HCO6=Yw_z?Ita5+I>LPfeN~ z<_jnsO8aoU)){py5pm{Y?x+4RIEc!<&M1qdrLlKkgR}0lWI(j>!tWoYPkaj?0nFv2 z|KRiYgCh)8qNUiLk9OP&ll)1-104IziK!K%=qDy_!UbR=TTk)ZsyGgd;=M3zD5&eh zTO`Gl5w$c^lh6G!!%>q@<)t5!bCa6*t-`G2K`An)KVkqoYjEugt$oqN70jd zN3~BqeV%4CYAAPY1@Ga>*adM#or`Cw!XhFQcN-ED6Wx^0F6V+cgGHKk>79M4_3-#^N{H+|_d((GNb|Gvzoqtp zxM*&?S7=YaCKJ4Sd|8}@{_qKsa~P^V|Mpos7&^T&QZo!KWaN!2w3FEyRTPANo{v|b zF|rtF|5yjWJYGxjn!o=~%Uf*EeCecf-wuBEs|qQY-^zS5GQD#of-3u7BqsCvKtta) zWge{nozu-X3Na5}zIM4%{AXhEi{xxzKCsXR4 z`SGP-zFjr}5E^yzL}Ehk`k0wh+Lw@99KI-Uc=PQAYsXjG$)e(7{r!T;gA<}>AA7hQ z#Xa`!3}-0l1J6?@`^;Xlw2gNla&(@}In|mSF|RZVu{RbC<=1i6h|z10iLy2Q**oQ| zn9||3rL!EDMa5>7?@w=tzmls$=Q57NeYeJ=`}UQyn4<(6NOf{mlhcHk|HKfzLqDLB z1#mM&$sQZ<{@mCY!RjB8&22`3|E}H<#UgF&l`f#&QTuWBY#kD&5K%nuA{~^+VRnP) z?<3P(LeLgO1ssob90HITpv$nQ9}T0OC~Cj#acDzIFP=c%q)%MHTBU+nzi;s{qg-7f z&i3-|Y{v7uy6s*${QJwlb+m*k)g_;{#J`sMjY4a-Lj|hq@;q`<4y-Td_g58K+P>aG zYcWFc_H_>K;%mFQVw1Kd^IB^PsY>i_$^c2mtrUkXBntgivkTAOkcQRbZr@*3 zl6!766wR&$;_wQ3DC5bIN5vlBzAWM}AQ2($;Ca_ zIg+uV&vk?dHCT9{GTbCv@oJ=EVj)eMqUWHVdve zNPeCZk4pTgDtn%tj@r<0SxRWltoE=PO$~<=I+X>u!hcffeTxlWd0xDGB~CG_@Hz6v zyiu}Ca5}dsl6d)v5{RJo3=c}z6h1MBkPoNZw8?(|P<;Kov))7@VL0TgR`%UaqY?QR zqR6k1XRoq}1e)*zXqE=abT$T?&-;ysz7dUASd!>R9$t+BPw_SvL&0lDI67I|*`2F( zl-exH|K)S8CLfOHo~VFSoLopz)9;!u^%Lq>u9cZF--R8`n&{uiOH?27)|nw$PLf4` z|G7V+=QtUXu97oKBI3R8OEVMx>bGcnmZ(r#6kQ)uRaImIG;9{Eby0+AM#x7WE{C#% zmfE`QMuc{-!E+k^PU*9f;+P53WGPA=Db*?2yAM<|J->qu_gMFZ%-2>a;jMHsRWFuZ|ps0NgCs4LuxK?7(Ti@+!kz9 zr3r}bDi`uRQh^MFYeFHvI6r&t57f7hXnWNK-|k1vxaIlyboUdDNLOLA62%avfZlqI ze}-+t2(f0(s|V=J^j9+z-q#x;%%HALO4b#M+UXc=uOzL8!sPyp4}e1X^Cn4>8YMuy z0S5%nuJ(LAwwR{NYnP}=GAP2w$915oue9mI6v(?s4>b6bPW@Eg`t`Wkqa%31W%6$- z9fo>huS6Fz&mh&B?paTP`#n^)>%P~0x4MGyvNs!vYt8(31Il#d?>tStrts-5{=>*a zadQAVeb%mVV8Zi+G?v#!MptYGkTu5LVGV~K*ZyUgqVlYWKZ}UkTPuB`^x(Lu3|-gu z#<|nZ`%F-`_B+v_*`9=#->IL-h}OvuD;j8e9e3GiXclwXXv!%rokM)EI<_kJL-bkv z?qY7Bc-76(_TZwiUD7#Wi7o17B(tS89kN(Lr9718Rnt}nk!P7a5Xgd2QRYMQQ=vsS zmy)3h^`qG?RhPeIMaGM+Q@gc&2IY#u-(K)mP+X0VCg$N2ap)<0CNuu$;XN~-bv>Hk zXiqn|@sP6c$8bM|CpKx_lAowWWxi?&`+768qO1 zm9*5cxED}eJz7FDzA{M=cFd29ONX-!7!hZ6N&Ok$S}b+P9i=ybm1e9IDd%AA`RoP% z_&t+Hjsq1V#e>tC_Vhr=+!s*M+Uw!`Elu)~CNbLfY!cshyxc-6#Dr+yV`Or_SHShh zC)PJ!+r*jV;SFs@B8GRkP)6stfz22xvtP@I9!CBxuf677=0aXM-lN!>8?&SaF37Ix z_iDlp>o{H}<|k?NUnN*zacR->pv&4^G*^r8<8*bkQ$7%AHRr)IJT(wkZ4e3@Dp2xq z8G_tKE@i$P<)BP^Yc0VC(|c87W=Bbl3EC-q1yP4X6mP?g&peE;>~N?ytzy%nwVy9etcKH ziK1J};k17uO9@%dj&5;&{0>Ot2Mv))h1#ct@~qIQlUI$xmM#A&16GH+n%m=>NIk_S zUwVSpmbZlqN3bk}-QQx&q)o0yB+gcozw9d?qZ3i~u?kpQ(R{bP*UOiwf5PQL)jwhX zr%2sZKKiZ_d=TDAxm>3^R;1Y%j%nuJdAS;b_fm8%XK6%!$C9Yk%#Qt}AHzX!8L+Cb zQooP1on_Nj{M;%1*2|sHc0p>!(_Bgr8C{&ep^4b<8l#)Y7>KMcxb2GF+@ZEm;~?=~ z-M7Vgz0c%%&NC&FDH`>xM_ms5TC5I#ZaH@9w9CG^4=1vKJ6&ka)QawSF!SL%P4 zL0>_iqZ*~gOPeAfF=wxWgkAo56k%Z)YlEkoCLYspvP#X;eUlkisg##Sjl+gBC_Xpb zzJ75ya$8q3Y0#+&nfIQ5An-eziYHf7`V9R{5haxH?dwnKVq2~=g*PgBEXl+*q8)m9 zZ_pLJG-)gucbZYw<4zhA-HDMH)vu!zTS3)pm{q8a#+`E6>=A1wEDhVXY^X(}7_{w@ zU<&@#WXL!Vj48H1V$gFi`{(1E)5YUD zlHiM@LWg9WTlrpHTF@NVRx{j6deq)ZB=4TS(Jzb9A3V7&cTf6fi zlf;Ti94(f6pySC~1XIHP*cpN!ON2*^S6{J-_q)E{5FqQU{p5(}7w!%6Oi^>@ud%6^!EqiB}&l z2}7cf@Ax0(!@q*Z|?%y!=&->NX$*}*&K6n-kOP-ZAZdp%b>w2ZefzEx~qaye8 z!NS$y&o9Dg?&ZnU`u*(GZD4}7EqX#-SU#SH;>Jj#^V|;f0b{xT5H{Df56mYmXKR;4 z;NVgGj?~OhE_oeN$f5nNliroU{xxZNmq(xXG&?n~cez8zhfMO06V#xUgI8!&d1^$E zVD@Ju?;E$ZmSdXJPHL$~G4hP5y5VGJVEJNS^^3Bcw#wc|4W;-oWj=t{G!VubZCY({ zVa&E$B{&|#MVxSRHTO}$rYm|TfY;o&VQVK}Mn<&bNG356NMyEqTVe5okV;!z9j4bK zL*-*S>epx(ziuLmWCQe`eH4`A?F~M|7nQJlVH(Sw^)_0;3>;XeYgg-)?x$}(n*lv} zndSjt;jh%b<_RnEFL5=ctH>orw}Z55wvjoD9^*+^q_I4$eVdGHl1kVXL^PYwE41uw z6)#-1gHvmoe!JwmVH*+w$9U{E@hmr0!ke+SSIR4cZp5=s>Myji&J{$e2u42)v`=>< zt@+PW2)Mt(-LqIowjH`U{LGLovmV~W-r{B zp`Pkzoh+`S8BRW$avD~bV14-Gq|9w-kMYB;rwdZ!$W65qMS}JwF9zr9HuK$m4K}j1 zvUAI8mrjv9v;^wkamf5B1KQ>eEUmxs@2!S5*%){Z3~$1I)OXr>;}+Yuc`S{Juf4zN zyLf)h1vRX(+## z%#tH>7;%dqzR&v%y4Qfc8>MOSI?%`j$0Aq7L>BPU3`M zOeG=PE)v@qalZ)dKtdg@inT6(>`G(~58fM>NGQ!m7Ory@BRS;k_o6VsjPrgVAQv+nX=A}_75W~t ztj$?==5;qsHTeUlVB`>49Cp-ZbVurzsTtPrc}zi%Y8&lR${A((Qg)B>7Ia%uX{C7o z8>42NJ=9v=Rdtv&eIEyEmTh-t=4u5)?>`GMbg|{x#dWy$n2x_av@~cy#}ukK3&-WZg*cd76nfPXJ>=%9g~#1HN-TE5%yw=|Iz$8|kK7hD!pKrZ z9Kn-JHTtvvHm3&2Y2UTUl;%GB0FrQ8Hsh z7$1tbZwf+`d`X>R%Iu+e{C2CskX*3MjnD_x%$%hb0T3#c9mDtF)3P~xGa8>_$Fuji zDEo;^xI95c+%`h~D^+q1J#1TvYiR{9N|UJ6K8^ewVVROhp_i!MVqg~ClIwA1?bQ!9 zja$oF#uOLzywAV?CXTz=&NDmTxg)52XfLdxo+?5~5pgC+xz=IMB=WWzqbY)3easUO zS`5Dc51*8^ms4;#jnJTrA!IPWcP)<)V0r8T%L?94&6HHT{t@E-Y2=PU(RcKK6Mv3Xib2&c~#KyAu#a%J9)P7`1sS9BV{?cFAf_*pktaoPVfj9 zU&o)pAagoB+RO|h78%DYKIg^i7?X?t>i^=t^HKX$;i}uoPEhUk#JpQy8oj0)v!0

7}BrprELz_9OW*&w6(CLNGi4*>eGe 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 K=d#Wzp$P!+aotb= literal 0 HcmV?d00001 From 05bc05a936c1bf7e7fdb5df67613b586ac726abc Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Thu, 14 Dec 2017 12:42:18 +0000 Subject: [PATCH 15/44] CORDA-716 Retire withTestSerialization (#2240) --- .../corda/core/utilities/NonEmptySetTest.kt | 15 ++-- .../amqp/SerializationOutputTests.kt | 9 ++- .../node/services/AttachmentLoadingTests.kt | 14 ++-- .../identity/InMemoryIdentityServiceTests.kt | 65 ++++++++--------- .../PersistentIdentityServiceTests.kt | 72 +++++++++---------- .../corda/irs/api/NodeInterestRatesTest.kt | 1 + .../corda/testing/SerializationTestHelpers.kt | 19 ----- .../InternalSerializationTestHelpers.kt | 19 +++++ 8 files changed, 115 insertions(+), 99 deletions(-) create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt 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/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 a3af4c26ce..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 @@ -54,6 +55,10 @@ class SerializationOutputTests { 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/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 2fc3f11770..60e781e9c4 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,16 +24,20 @@ 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.rigorousMock -import net.corda.testing.withTestSerialization import org.junit.Assert.assertEquals +import org.junit.Rule import org.junit.Test import java.net.URLClassLoader import java.nio.file.Files import kotlin.test.assertFailsWith class AttachmentLoadingTests { + @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() @@ -80,7 +84,7 @@ class AttachmentLoadingTests { } @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) @@ -96,7 +100,7 @@ class AttachmentLoadingTests { } @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() @@ -104,15 +108,17 @@ class AttachmentLoadingTests { 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/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt index d7c4fa8bbc..21c9330758 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 @@ -34,6 +35,10 @@ class InMemoryIdentityServiceTests { fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities.toSet(), 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 46dc9b5f50..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 @@ -41,6 +42,9 @@ class PersistentIdentityServiceTests { 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/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 3c8b0192f3..019b91c128 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,7 @@ 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.node.* import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.junit.After diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 608db48d82..675e82eda0 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -55,25 +55,6 @@ interface GlobalSerializationEnvironment : SerializationEnvironment { fun unset() } -/** @param inheritable whether new threads inherit the environment, use sparingly. */ -fun withTestSerialization(inheritable: Boolean = false, callable: (SerializationEnvironment) -> T): T { - return createTestSerializationEnv("").asContextEnv(inheritable, callable) -} - -/** - * For example your test class uses [SerializationEnvironmentRule] but you want to turn it off for one method. - * Use sparingly, ideally a test class shouldn't mix serializers init mechanisms. - */ -fun withoutTestSerialization(callable: () -> T): T { - val (property, env) = listOf(_contextSerializationEnv, _inheritableContextSerializationEnv).map { Pair(it, it.get()) }.single { it.second != null } - property.set(null) - try { - return callable() - } finally { - property.set(env) - } -} - /** * Should only be used by Driver and MockNode. * @param armed true to install, false to do nothing and return a dummy env. diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt new file mode 100644 index 0000000000..659c889087 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt @@ -0,0 +1,19 @@ +package net.corda.testing.internal + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.internal._inheritableContextSerializationEnv +import net.corda.testing.SerializationEnvironmentRule + +/** + * For example your test class uses [SerializationEnvironmentRule] but you want to turn it off for one method. + * Use sparingly, ideally a test class shouldn't mix serializers init mechanisms. + */ +fun withoutTestSerialization(callable: () -> T): T { // TODO: Delete this, see CORDA-858. + val (property, env) = listOf(_contextSerializationEnv, _inheritableContextSerializationEnv).map { Pair(it, it.get()) }.single { it.second != null } + property.set(null) + try { + return callable() + } finally { + property.set(env) + } +} From dbf6862f32c92b1adfbb44f2c5b3dd0958196430 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 14 Dec 2017 14:01:21 +0000 Subject: [PATCH 16/44] Adds debugging info for contract constraints, and links to it from error message. --- .../MissingContractAttachments.kt | 3 +- docs/source/api-contract-constraints.rst | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) 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/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 From 2aac969463c64f20f84dfdd40cd8787e7a96e4d9 Mon Sep 17 00:00:00 2001 From: igor nitto Date: Thu, 14 Dec 2017 14:09:26 +0000 Subject: [PATCH 17/44] Minor fix to "API: Vault Query" docsite page (#1910) * Minor fix to "API: Vault Query" docsite page * Some more typo found in documentation --- docs/source/api-vault-query.rst | 4 ++-- docs/source/versioning.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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/versioning.rst b/docs/source/versioning.rst index 9f5967e1ef..ec6d4dfae1 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`` From 409cefd467883c537d0ab7af809a4912eaf4d89d Mon Sep 17 00:00:00 2001 From: igor nitto Date: Thu, 14 Dec 2017 15:42:34 +0000 Subject: [PATCH 18/44] CORDA-827: more doc changes (#2254) * CORDA-827: more doc changes * CORDA-827: more doc changes --- docs/source/clientrpc.rst | 66 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/docs/source/clientrpc.rst b/docs/source/clientrpc.rst index 7f8322bbfb..ae30828149 100644 --- a/docs/source/clientrpc.rst +++ b/docs/source/clientrpc.rst @@ -71,23 +71,19 @@ Fine grained permissions allow a user to invoke a specific RPC operation, or to RPC security management ----------------------- -Hard coding user accounts in the ``rpcUsers`` field provides a quick way of allowing node's RPC to be accessed by a fixed -set of authenticated users but has some obvious shortcomings. To support use cases aiming for higher security and flexibility, -Corda RPC security system offers additional features such as: +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 external data source (e.g.: a remote RDBMS), with optional caching - in node memory. In particular, this allows user credentials and permissions externally to be updated externally without - requiring node's restart. + * 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 and defined by the `Apache Shiro framework `_ + 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 a node configuration. - -.. warning:: The ``rpcUsers`` field is now deprecated in favour of the set the ``security`` config structure. A node - configuration specifying both ``rpcUsers`` and ``security`` fields will trigger an exception during node startup. - -The following example configuration points the node to a remote RDBMS storing hash-encrypted passwords and enable caching -of user data in node's memory: +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 @@ -114,8 +110,8 @@ of user data in node's memory: } } -Moreover, for practical reasons, we can still have an hard-coded static list of users embedded in the ``security`` -structure like in the old ``rpcUsers`` format, by specifying a ``dataSource`` of ``INMEMORY`` type: +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 @@ -137,33 +133,35 @@ structure like in the old ``rpcUsers`` format, by specifying a ``dataSource`` of } } +.. 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`` field defines the data provider supplying credentials and permissions for users. It currently exists -in two forms, identified by the subfield ``type``: +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 list of user credentials and permissions hard-coded in configuration in the ``users`` field (see example above) + :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``. The current implementation - expect the database to store data according to the following schema: + :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 ``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 + permission strings. - Unlike the ``INMEMORY`` case, in the user database permissions are assigned to *roles* rather than individual users. - - .. note:: There is no prescription on the SQL type of the columns (although our tests were conducted on ``username`` and + .. 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 production environment where security is critical. Passwords are assumed -to be in plain format by default, unless a different format is specified ny the ``passwordEncryption`` field, like: +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 @@ -173,13 +171,13 @@ to be in plain format by default, unless a different format is specified ny the ``SHIRO_1_CRYPT`` identifies the `Apache Shiro fully reversible Modular Crypt Format `_, -currently the only non-plain password hash-encryption format supported by Corda. Passwords can be hash-encrypted in this -format using the `Apache Shiro Hasher command line tool `_. +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 users data -^^^^^^^^^^^^^^^^^^ +Caching user accounts data +^^^^^^^^^^^^^^^^^^^^^^^^^^ -A cache layer on top of the external data source of users credentials and permissions can significantly benefit +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: @@ -196,7 +194,7 @@ for example: } This will enable a non-persistent cache contained in the node's memory with maximum number of entries set to ``maxEntries`` -with entries expiring and refreshed after ``expireAfterSecs`` number of seconds. +where entries are expired and refreshed after ``expireAfterSecs`` seconds. Observables ----------- From 5e60aa1c55dac7dcfb71ee8e8ea26c1090fa41a4 Mon Sep 17 00:00:00 2001 From: Tommy Lillehagen Date: Thu, 14 Dec 2017 13:34:01 +0000 Subject: [PATCH 19/44] ENT-1249 - Downgrade to version 2.1 instead --- build.gradle | 17 ++++++++++++++++- .../net/corda/client/rpc/RPCConcurrencyTests.kt | 2 +- .../corda/testing/node/internal/RPCDriver.kt | 8 ++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 2cc2237b9e..490068e188 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,22 @@ buildscript { ext.capsule_version = '1.0.1' ext.asm_version = '0.5.3' - ext.artemis_version = '2.2.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.1.0 (version used prior to the 2.4 + * bump). + * + * 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.1.0' ext.jackson_version = '2.9.2' ext.jetty_version = '9.4.7.v20170914' ext.jersey_version = '2.25' 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..b3975f3516 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 @@ -12,7 +12,7 @@ import net.corda.node.services.messaging.RPCServerConfiguration 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.apache.activemq.artemis.utils.ConcurrentHashSet import org.junit.After import org.junit.Test import org.junit.runner.RunWith diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index 21f0675273..4d9331f2bf 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -46,12 +46,13 @@ import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl 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.Connection +import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3 import java.lang.reflect.Method import java.nio.file.Path import java.nio.file.Paths import java.util.* +import javax.security.cert.X509Certificate inline fun RPCDriverDSL.startInVmRpcClient( username: String = rpcTestUser.username, @@ -131,14 +132,13 @@ fun rpcDriver( } private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityManager3 { - override fun validateUser(user: String?, password: String?) = isValid(user, password) override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?) = isValid(user, password) - override fun validateUser(user: String?, password: String?, remotingConnection: Connection?): String? { + override fun validateUser(user: String?, password: String?, certificates: Array?): String? { return validate(user, password) } - override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?, address: String?, connection: Connection?): String? { + override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?, address: String?, connection: RemotingConnection?): String? { return validate(user, password) } From 21e1118ea04f5da324b8b3434c39ea606af2910c Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Thu, 14 Dec 2017 16:30:18 +0000 Subject: [PATCH 20/44] Make test constants Java-visible fields. (#2258) --- .../client/rpc/CordaRPCJavaClientTest.java | 4 ++-- .../net/corda/core/flows/FlowsInJavaTest.java | 4 ++-- .../tutorial/testdsl/CommercialPaperTest.java | 22 +++++++++---------- .../contracts/asset/CashTestsJava.java | 4 ++-- .../services/vault/VaultQueryJavaTests.java | 12 +++++----- .../kotlin/net/corda/testing/TestConstants.kt | 11 +++++++++- 6 files changed, 33 insertions(+), 24 deletions(-) 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 c4492eca06..a3796af894 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 @@ -28,7 +28,7 @@ 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.ALICE_NAME; public class CordaRPCJavaClientTest extends NodeBasedTest { public CordaRPCJavaClientTest() { @@ -56,7 +56,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest { @Before public void setUp() throws ExecutionException, InterruptedException { - 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/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java index 250877bde4..2a536b7f0d 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -26,8 +26,8 @@ public class FlowsInJavaTest { @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/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 573bef81b3..0a361a7b95 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 @@ -28,11 +28,11 @@ 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(); + private static final Party DUMMY_NOTARY = new TestIdentity(DUMMY_NOTARY_NAME, 20L).getParty(); @Rule public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule(); private final byte[] defaultRef = {123}; @@ -52,7 +52,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 @@ -131,11 +131,11 @@ public class CommercialPaperTest { 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.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); - tx.timeWindow(getTEST_TX_TIME()); + tx.timeWindow(TEST_TX_TIME); return tx.verifies(); }); return Unit.INSTANCE; @@ -151,11 +151,11 @@ public class CommercialPaperTest { 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.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); - tx.timeWindow(getTEST_TX_TIME()); + tx.timeWindow(TEST_TX_TIME); return tx.verifies(); }); } @@ -178,7 +178,7 @@ public class CommercialPaperTest { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); 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(); }); @@ -214,7 +214,7 @@ public class CommercialPaperTest { tx.output(Cash.PROGRAM_ID, "paper", getPaper()); 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(); }); @@ -260,7 +260,7 @@ public class CommercialPaperTest { tx.output(Cash.PROGRAM_ID, "paper", getPaper()); 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(); }); 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 b9daac0541..7bde2efedd 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 @@ -16,14 +16,14 @@ 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.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); 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 f6fa7aa18a..45ae11c758 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 @@ -46,19 +46,19 @@ import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE 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.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(); diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index fc4ee20cd4..f601f988d2 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -13,14 +13,23 @@ import java.security.PublicKey import java.time.Instant // A dummy time at which we will be pretending test transactions are created. -val TEST_TX_TIME: Instant get() = Instant.parse("2015-04-17T12:00:00.00Z") +@JvmField +val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z") +@JvmField val DUMMY_NOTARY_NAME = CordaX500Name("Notary Service", "Zurich", "CH") +@JvmField val DUMMY_BANK_A_NAME = CordaX500Name("Bank A", "London", "GB") +@JvmField val DUMMY_BANK_B_NAME = CordaX500Name("Bank B", "New York", "US") +@JvmField val DUMMY_BANK_C_NAME = CordaX500Name("Bank C", "Tokyo", "JP") +@JvmField val BOC_NAME = CordaX500Name("BankOfCorda", "London", "GB") +@JvmField val ALICE_NAME = CordaX500Name("Alice Corp", "Madrid", "ES") +@JvmField val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT") +@JvmField val CHARLIE_NAME = CordaX500Name("Charlie Ltd", "Athens", "GR") val DEV_CA: CertificateAndKeyPair by lazy { // TODO: Should be identity scheme From 8dfe377ae1443e1acbd4dccd99ffd69ac7c06972 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 15 Dec 2017 10:13:18 +0000 Subject: [PATCH 21/44] Corda-862 - Fix notary demo for AMQP --- .../main/kotlin/net/corda/notarydemo/flows/DummyIssueAndMove.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From 550469ea38cd90c6f8e402446be25cd0b72abaf8 Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Fri, 15 Dec 2017 11:13:15 +0000 Subject: [PATCH 22/44] Wire part of network parameters (#2187) * Take maximum message size from network parameters * Add epoch handling * Add handling of network parameters mismatch Change NetworkMapClient and updater, add handle in AbstractNode that results in node shutdown on parameters mismatch. Later on we should implement proper handling of parameters updates. Add tests of NetworkParameters wiring. When node starts with compatibilityZone url configured it takes networkParameters from the networkMap. * Permit only one network parameters file On node startup network parameters are read from node's base directory, we permit only zero or one files to be there. If network map server is configured the parameters can be downloaded at startup (if not present in the directory already). * Update docs on network map endpoints --- docs/source/network-map.rst | 39 ++++++++-- .../net/corda/nodeapi/internal/NetworkMap.kt | 14 ++-- .../internal/NetworkParametersCopier.kt | 3 +- .../internal/NetworkParametersGenerator.kt | 1 - .../node/services/network/NetworkMapTest.kt | 31 +++++++- .../registration/NodeRegistrationTest.kt | 10 ++- .../net/corda/node/internal/AbstractNode.kt | 44 ++++++++--- .../kotlin/net/corda/node/internal/Node.kt | 9 ++- .../messaging/ArtemisMessagingClient.kt | 4 +- .../messaging/ArtemisMessagingServer.kt | 15 ++-- .../services/messaging/P2PMessagingClient.kt | 5 +- .../services/messaging/RPCMessagingClient.kt | 4 +- .../messaging/VerifierMessagingClient.kt | 4 +- .../node/services/network/NetworkMapClient.kt | 12 ++- .../network/PersistentNetworkMapCache.kt | 4 - .../node/internal/NetworkParametersTest.kt | 73 +++++++++++++++++++ .../messaging/ArtemisMessagingTests.kt | 10 +-- .../services/network/NetworkMapClientTest.kt | 4 +- .../services/network/NetworkMapUpdaterTest.kt | 16 ++-- .../kotlin/net/corda/testing/node/MockNode.kt | 11 ++- .../testing/node/internal/DriverDSLImpl.kt | 5 +- .../corda/testing/node/internal/RPCDriver.kt | 19 ++--- .../testing/node/network/NetworkMapServer.kt | 23 +++--- .../common/internal/ParametersUtilities.kt | 21 ++++-- .../kotlin/net/corda/testing/TestConstants.kt | 3 + .../corda/demobench/model/NodeController.kt | 1 - 26 files changed, 276 insertions(+), 109 deletions(-) create mode 100644 node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index 34356bad4f..8ec565a95d 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, @@ -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. @@ -55,3 +66,19 @@ Nodes expect to find a serialized ``SignedData`` object, the same obje 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/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt index 9fcd59ea95..e29e0f6ca0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt @@ -11,9 +11,9 @@ 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 +const val NETWORK_PARAMS_FILE_NAME = "network-parameters" // TODO: Need more discussion on rather we should move this class out of internal. /** * Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes. @@ -22,21 +22,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, @@ -46,6 +46,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" } } } 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 index d464575a4f..954dac862a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt @@ -6,7 +6,6 @@ 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 @@ -24,7 +23,7 @@ class NetworkParametersCopier(networkParameters: NetworkParameters) { fun install(dir: Path) { try { - serializedNetworkParameters.open().copyTo(dir / "network-parameters") + serializedNetworkParameters.open().copyTo(dir / NETWORK_PARAMS_FILE_NAME) } catch (e: FileAlreadyExistsException) { // Leave the file untouched if it already exists } 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 index 59ce2eb9d4..fd0bcbda20 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt @@ -41,7 +41,6 @@ class NetworkParametersGenerator { minimumPlatformVersion = 1, notaries = notaryInfos, modifiedTime = Instant.now(), - eventHorizon = 10000.days, maxMessageSize = 40000, maxTransactionSize = 40000, epoch = 1 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 faa30be62b..329e259714 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,9 +1,16 @@ package net.corda.node.services.network +import net.corda.core.crypto.SignedData +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_PARAMS_FILE_NAME +import net.corda.nodeapi.internal.NetworkParameters import net.corda.testing.node.internal.CompatibilityZoneParams import net.corda.testing.ALICE_NAME +import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.BOB_NAME import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation @@ -12,10 +19,17 @@ import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import java.net.URL +import java.nio.file.Files +import kotlin.streams.toList +import kotlin.test.assertEquals class NetworkMapTest { + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule(true) private val cacheTimeout = 1.seconds private val portAllocation = PortAllocation.Incremental(10000) @@ -34,9 +48,20 @@ class NetworkMapTest { networkMapServer.close() } + @Test + fun `node correctly downloads and saves network parameters file on startup`() { + internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { + val aliceDir = baseDirectory(ALICE_NAME) + startNode(providedName = ALICE_NAME).getOrThrow() + val networkParameters = Files.list(aliceDir).toList().single { NETWORK_PARAMS_FILE_NAME == it.fileName.toString() } + .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() @@ -51,7 +76,7 @@ class NetworkMapTest { @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() @@ -72,7 +97,7 @@ class NetworkMapTest { @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/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index 2acd637b5d..a59809eb8e 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,6 +14,7 @@ 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.SerializationEnvironmentRule import net.corda.testing.node.internal.CompatibilityZoneParams import net.corda.testing.driver.PortAllocation import net.corda.testing.node.internal.internalDriver @@ -24,6 +25,7 @@ import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import java.io.ByteArrayOutputStream import java.io.InputStream @@ -38,6 +40,9 @@ import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response class NodeRegistrationTest { + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule(true) private val portAllocation = PortAllocation.Incremental(13000) private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() private val registrationHandler = RegistrationHandler(rootCertAndKeyPair) @@ -47,7 +52,7 @@ class NodeRegistrationTest { @Before fun startServer() { - server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler) + server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), rootCertAndKeyPair, registrationHandler) serverHostAndPort = server.start() } @@ -64,7 +69,8 @@ class NodeRegistrationTest { 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/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 3e1ab5150a..220bb62259 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -57,6 +57,7 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor +import net.corda.nodeapi.internal.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.NetworkParameters import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -69,6 +70,7 @@ import rx.Observable import rx.Scheduler import java.io.IOException import java.lang.reflect.InvocationTargetException +import java.nio.file.Files import java.security.KeyPair import java.security.KeyStoreException import java.security.PublicKey @@ -82,6 +84,7 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.SECONDS import kotlin.collections.set import kotlin.reflect.KClass +import kotlin.streams.toList import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair /** @@ -135,11 +138,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 @@ -197,10 +196,13 @@ 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) + } + readNetworkParameters() // 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) @@ -238,10 +240,10 @@ 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) { @@ -636,10 +638,28 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } 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" } + val files = Files.list(configuration.baseDirectory).filter { NETWORK_PARAMS_FILE_NAME == it.fileName.toString() }.toList() + val paramsFromFile = try { + // It's fine at this point if we don't have network parameters or have corrupted file, later we check if parameters can be downloaded from network map server. + files[0].readAll().deserialize>().verified() + } catch (t: Exception) { + log.warn("Couldn't find correct network parameters file in the base directory") + null + } + networkParameters = if (paramsFromFile != null) { + paramsFromFile + } else if (networkMapClient != null) { + log.info("Requesting network parameters from network map server...") + val (networkMap, _) = networkMapClient!!.getNetworkMap() + val signedParams = networkMapClient!!.getNetworkParameter(networkMap.networkParameterHash) ?: throw IllegalArgumentException("Failed loading network parameters from network map server") + val verifiedParams = signedParams.verified() // Verify before saving. + signedParams.serialize().open().copyTo(configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME) + verifiedParams + } else { + throw IllegalArgumentException("Couldn't load network parameters file") + } + 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 ecd96b52e9..201f31a20b 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/services/messaging/ArtemisMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt index fd36dc9ba7..9a160138c1 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 @@ -10,7 +10,7 @@ import net.corda.nodeapi.internal.config.SSLConfiguration import org.apache.activemq.artemis.api.core.client.* import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE -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 +30,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() 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..4b80354fd2 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 @@ -101,14 +101,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 } @@ -181,9 +178,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,7 +208,7 @@ 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 } ) 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 f4976610d8..74c183fd5c 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 @@ -68,7 +68,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() @@ -146,7 +147,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 = CopyOnWriteArrayList() 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 436c56fdaa..5146c2bc53 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 @@ -66,7 +66,7 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C } } - 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 @@ -85,7 +85,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 @@ -125,6 +126,12 @@ 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 { @@ -144,7 +151,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) 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 0112288aa9..1d2e9d1a36 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 @@ -207,9 +207,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)) @@ -292,7 +289,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/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..9bb49eee12 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt @@ -0,0 +1,73 @@ +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.NetworkParameters +import net.corda.nodeapi.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NotaryInfo +import net.corda.testing.* +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.node.* +import org.junit.After +import org.junit.Test +import java.nio.file.Path +import kotlin.test.assertFails +import org.assertj.core.api.Assertions.* + +class NetworkParametersTest { + private val mockNet = MockNetwork( + 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/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 628bef9087..e4136ffc24 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 @@ -163,7 +163,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, @@ -171,16 +171,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/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index 04df884285..23bed3ec85 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 @@ -6,9 +6,7 @@ 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.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 @@ -71,7 +69,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..ffee923ea9 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 @@ -28,6 +28,10 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertEquals class NetworkMapUpdaterTest { + companion object { + val NETWORK_PARAMS_HASH = SecureHash.randomSHA256() + } + @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) @@ -53,7 +57,7 @@ class NetworkMapUpdaterTest { val scheduler = TestScheduler() val fileWatcher = NodeInfoWatcher(baseDir, scheduler) - val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient) + val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, NETWORK_PARAMS_HASH) // Publish node info for the first time. updater.updateNodeInfo(nodeInfo1) { signedNodeInfo } @@ -96,13 +100,13 @@ class NetworkMapUpdaterTest { 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 { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), NETWORK_PARAMS_HASH), 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 updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, NETWORK_PARAMS_HASH) // Test adding new node. networkMapClient.publish(nodeInfo1) @@ -150,13 +154,13 @@ class NetworkMapUpdaterTest { 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 { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), NETWORK_PARAMS_HASH), 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 updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, NETWORK_PARAMS_HASH) // Add all nodes. NodeInfoWatcher.saveToFile(baseDir / CordformNode.NODE_INFO_DIRECTORY, fileNodeInfo) @@ -200,7 +204,7 @@ class NetworkMapUpdaterTest { val scheduler = TestScheduler() val fileWatcher = NodeInfoWatcher(baseDir, scheduler) - val updater = NetworkMapUpdater(networkMapCache, fileWatcher, null) + val updater = NetworkMapUpdater(networkMapCache, fileWatcher, null, NETWORK_PARAMS_HASH) // Not subscribed yet. verify(networkMapCache, times(0)).addNode(any()) 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 96d3683a95..c3c510b0b7 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,6 +22,7 @@ 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.node.VersionInfo import net.corda.core.utilities.seconds import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode @@ -91,7 +92,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) @@ -102,7 +104,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 ) /** @@ -241,7 +244,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 ) { @@ -392,7 +395,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties parameters.configOverrides(it) } - val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot)) + val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version)) _nodes += node if (start) { node.start() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index cf716e1bf2..288c63ef4c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -245,6 +245,7 @@ class DriverDSLImpl( } internal fun startCordformNodes(cordforms: List): CordaFuture<*> { + check(compatibilityZone == null) { "Cordform nodes should be run without compatibilityZone configuration" } val clusterNodes = HashMultimap.create() val notaryInfos = ArrayList() @@ -354,7 +355,7 @@ class DriverDSLImpl( } val notaryInfos = generateNotaryIdentities() // The network parameters must be serialised before starting any of the nodes - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + if (compatibilityZone == null) networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) val nodeHandles = startNotaries() _notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) } } @@ -519,7 +520,7 @@ class DriverDSLImpl( val configuration = config.parseAsNodeConfiguration() val baseDirectory = configuration.baseDirectory.createDirectories() nodeInfoFilesCopier?.addConfig(baseDirectory) - networkParameters!!.install(baseDirectory) + networkParameters?.install(baseDirectory) val onNodeExit: () -> Unit = { nodeInfoFilesCopier?.removeConfig(baseDirectory) countObservables.remove(configuration.myLegalName) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index 4d9331f2bf..72a5bc8143 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -25,6 +25,7 @@ import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.RPCApi import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT +import net.corda.testing.MAX_MESSAGE_SIZE import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.PortAllocation import net.corda.testing.node.NotarySpec @@ -227,8 +228,8 @@ data class RPCDriverDSL( fun startInVmRpcServer( rpcUser: User = rpcTestUser, nodeLegalName: CordaX500Name = fakeNodeLegalName, - maxFileSize: Int = ArtemisMessagingServer.MAX_FILE_SIZE, - maxBufferedBytesPerClient: Long = 10L * ArtemisMessagingServer.MAX_FILE_SIZE, + maxFileSize: Int = MAX_MESSAGE_SIZE, + maxBufferedBytesPerClient: Long = 10L * MAX_MESSAGE_SIZE, configuration: RPCServerConfiguration = RPCServerConfiguration.default, ops: I ): CordaFuture { @@ -295,8 +296,8 @@ data class RPCDriverDSL( serverName: String = "driver-rpc-server-${random63BitValue()}", rpcUser: User = rpcTestUser, nodeLegalName: CordaX500Name = fakeNodeLegalName, - maxFileSize: Int = ArtemisMessagingServer.MAX_FILE_SIZE, - maxBufferedBytesPerClient: Long = 10L * ArtemisMessagingServer.MAX_FILE_SIZE, + maxFileSize: Int = MAX_MESSAGE_SIZE, + maxBufferedBytesPerClient: Long = 10L * MAX_MESSAGE_SIZE, configuration: RPCServerConfiguration = RPCServerConfiguration.default, customPort: NetworkHostAndPort? = null, ops: I @@ -378,8 +379,8 @@ data class RPCDriverDSL( fun startRpcBroker( serverName: String = "driver-rpc-server-${random63BitValue()}", rpcUser: User = rpcTestUser, - maxFileSize: Int = ArtemisMessagingServer.MAX_FILE_SIZE, - maxBufferedBytesPerClient: Long = 10L * ArtemisMessagingServer.MAX_FILE_SIZE, + maxFileSize: Int = MAX_MESSAGE_SIZE, + maxBufferedBytesPerClient: Long = 10L * MAX_MESSAGE_SIZE, customPort: NetworkHostAndPort? = null ): CordaFuture { val hostAndPort = customPort ?: driverDSL.portAllocation.nextHostAndPort() @@ -402,8 +403,8 @@ data class RPCDriverDSL( fun startInVmRpcBroker( rpcUser: User = rpcTestUser, - maxFileSize: Int = ArtemisMessagingServer.MAX_FILE_SIZE, - maxBufferedBytesPerClient: Long = 10L * ArtemisMessagingServer.MAX_FILE_SIZE + maxFileSize: Int = MAX_MESSAGE_SIZE, + maxBufferedBytesPerClient: Long = 10L * MAX_MESSAGE_SIZE ): CordaFuture { return driverDSL.executorService.fork { val artemisConfig = createInVmRpcServerArtemisConfig(maxFileSize, maxBufferedBytesPerClient) @@ -431,7 +432,7 @@ data class RPCDriverDSL( brokerHandle: RpcBrokerHandle ): RpcServerHandle { val locator = ActiveMQClient.createServerLocatorWithoutHA(brokerHandle.clientTransportConfiguration).apply { - minLargeMessageSize = ArtemisMessagingServer.MAX_FILE_SIZE + minLargeMessageSize = MAX_MESSAGE_SIZE isUseGlobalPools = false } val rpcSecurityManager = RPCSecurityManagerImpl.fromUserList(users = listOf(rpcUser), id = AuthServiceId("TEST_SECURITY_MANAGER")) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt index 4e6eb0a83e..70abb19281 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt @@ -1,16 +1,12 @@ package net.corda.testing.node.network -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.SignedData -import net.corda.core.crypto.sha256 +import net.corda.core.crypto.* import net.corda.core.internal.cert import net.corda.core.internal.toX509CertHolder import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.hours import net.corda.nodeapi.internal.DigitalSignatureWithCert import net.corda.nodeapi.internal.NetworkMap import net.corda.nodeapi.internal.NetworkParameters @@ -39,9 +35,11 @@ import javax.ws.rs.core.Response.ok class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort, + root_ca: CertificateAndKeyPair = ROOT_CA, // Default to ROOT_CA for testing. vararg additionalServices: Any) : Closeable { companion object { - val stubNetworkParameter = NetworkParameters(1, emptyList(), 1.hours, 10, 10, Instant.now(), 10) + val stubNetworkParameter = NetworkParameters(1, emptyList(), 40000, 40000, Instant.now(), 10) + private val serializedParameters = stubNetworkParameter.serialize() private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair { val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) @@ -56,9 +54,7 @@ class NetworkMapServer(cacheTimeout: Duration, } private val server: Server - // Default to ROOT_CA for testing. - // TODO: make this configurable? - private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(ROOT_CA)) + private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(root_ca)) init { server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply { @@ -100,6 +96,11 @@ class NetworkMapServer(cacheTimeout: Duration, @Path("network-map") class InMemoryNetworkMapService(private val cacheTimeout: Duration, private val networkMapKeyAndCert: CertificateAndKeyPair) { private val nodeInfoMap = mutableMapOf>() + private val parametersHash = serializedParameters.hash + private val signedParameters = SignedData( + serializedParameters, + DigitalSignature.WithKey(networkMapKeyAndCert.keyPair.public, Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedParameters.bytes)) + ) @POST @Path("publish") @@ -115,7 +116,7 @@ class NetworkMapServer(cacheTimeout: Duration, @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNetworkMap(): Response { - val networkMap = NetworkMap(nodeInfoMap.keys.map { it }, SecureHash.randomSHA256()) + val networkMap = NetworkMap(nodeInfoMap.keys.map { it }, parametersHash) val serializedNetworkMap = networkMap.serialize() val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes) val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate.cert, signature)) @@ -143,7 +144,7 @@ class NetworkMapServer(cacheTimeout: Duration, @Path("network-parameter/{var}") @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNetworkParameter(@PathParam("var") networkParameterHash: String): Response { - return Response.ok(stubNetworkParameter.serialize().bytes).build() + return Response.ok(signedParameters.serialize().bytes).build() } @GET diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index ea84cd88c8..13ca04f95c 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -1,18 +1,23 @@ package net.corda.testing.common.internal -import net.corda.core.utilities.days import net.corda.nodeapi.internal.NetworkParameters import net.corda.nodeapi.internal.NotaryInfo import java.time.Instant -fun testNetworkParameters(notaries: List): NetworkParameters { +fun testNetworkParameters( + notaries: List, + minimumPlatformVersion: Int = 1, + modifiedTime: Instant = Instant.now(), + maxMessageSize: Int = 1048576, + maxTransactionSize: Int = 40000, + epoch: Int = 1 +): NetworkParameters { return NetworkParameters( - minimumPlatformVersion = 1, + minimumPlatformVersion = minimumPlatformVersion, notaries = notaries, - modifiedTime = Instant.now(), - eventHorizon = 10000.days, - maxMessageSize = 40000, - maxTransactionSize = 40000, - epoch = 1 + modifiedTime = modifiedTime, + maxMessageSize = maxMessageSize, + maxTransactionSize = maxTransactionSize, + epoch = epoch ) } \ No newline at end of file diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index f601f988d2..cf147d6c90 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -51,3 +51,6 @@ val DEV_TRUST_ROOT: X509CertificateHolder by lazy { fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) = Command(DummyCommandData, signers.toList()) object DummyCommandData : TypeOnlyCommandData() + +/** Maximum artemis message size. 10 MiB maximum allowed file size for attachments, including message headers. */ +const val MAX_MESSAGE_SIZE: Int = 1048576 diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index fd1be25923..834b8a0ae3 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -143,7 +143,6 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { minimumPlatformVersion = 1, notaries = listOf(NotaryInfo(identity, config.nodeConfig.notary!!.validating)), modifiedTime = Instant.now(), - eventHorizon = 10000.days, maxMessageSize = 40000, maxTransactionSize = 40000, epoch = 1 From 479a6564848d481a0b302286484929ceacbdcbc0 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 15 Dec 2017 13:15:05 +0000 Subject: [PATCH 23/44] CORDA-716 Consistent MockServices API (#2247) * Consistent MockServices API. * Fix compile error. --- .../TransactionSerializationTests.kt | 3 +- .../finance/contracts/CommercialPaperTests.kt | 14 ++++---- .../finance/contracts/asset/CashTests.kt | 6 ++-- .../services/vault/VaultQueryJavaTests.java | 6 ++-- .../services/vault/NodeVaultServiceTest.kt | 5 ++- .../node/services/vault/VaultQueryTests.kt | 6 ++-- .../node/services/vault/VaultWithCashTest.kt | 10 +++--- .../net/corda/testing/node/MockServices.kt | 35 +++++++++---------- .../kotlin/net/corda/testing/CoreTestUtils.kt | 6 ++-- 9 files changed, 42 insertions(+), 49 deletions(-) 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 1fc305f656..736b38891f 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -124,8 +124,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/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt index dde66f4216..a069b46537 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -266,10 +266,9 @@ class CommercialPaperTestsGeneric { @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"), - MEGA_CORP.name) + makeTestIdentityService(listOf(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 +278,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"), - MEGA_CORP.name) + makeTestIdentityService(listOf(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 +306,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 e085602d45..b81aba2395 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 @@ -99,10 +98,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"), - CordaX500Name("Me", "London", "GB")) + makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), + TestIdentity(CordaX500Name("Me", "London", "GB"))) database = databaseAndServices.first ourServices = databaseAndServices.second 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 45ae11c758..cf930962ea 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 @@ -72,10 +72,10 @@ public class VaultQueryJavaTests { List cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); IdentityServiceInternal identitySvc = makeTestIdentityService(Arrays.asList(MEGA_CORP.getIdentity(), DUMMY_CASH_ISSUER_INFO.getIdentity(), DUMMY_NOTARY.getIdentity())); Pair databaseAndServices = makeTestDatabaseAndMockServices( - Arrays.asList(MEGA_CORP.getKeyPair(), DUMMY_NOTARY.getKeyPair()), - identitySvc, cordappPackages, - MEGA_CORP.getName()); + 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(); 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 a8f24d73cc..492067a7ba 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 @@ -86,10 +86,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(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), + megaCorp) database = databaseAndServices.first services = databaseAndServices.second vaultFiller = VaultFiller(services, dummyNotary) 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 19ccd230bb..670faaf85b 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 @@ -104,10 +104,10 @@ class VaultQueryTests { 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(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)), + megaCorp, + DUMMY_NOTARY_KEY) database = databaseAndServices.first services = databaseAndServices.second vaultFiller = VaultFiller(services, dummyNotary) 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 4e1269fc14..2d2932aa01 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 @@ -56,6 +56,7 @@ class VaultWithCashTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) + private val servicesKey = generateKeyPair() lateinit var services: MockServices private lateinit var vaultFiller: VaultFiller lateinit var issuerServices: MockServices @@ -68,10 +69,10 @@ class VaultWithCashTest { fun setUp() { LogHelper.setLevel(VaultWithCashTest::class) val databaseAndServices = makeTestDatabaseAndMockServices( - listOf(generateKeyPair(), dummyNotary.keyPair), - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)), cordappPackages, - MEGA_CORP.name) + makeTestIdentityService(listOf(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) @@ -98,8 +99,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/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index a49b934afd..cddc41827b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -53,8 +53,8 @@ open class MockServices private constructor( cordappLoader: CordappLoader, override val validatedTransactions: WritableTransactionStorage, override val identityService: IdentityServiceInternal, - private val initialIdentityName: CordaX500Name, - val keys: Array + private val initialIdentity: TestIdentity, + private val moreKeys: Array ) : ServiceHub, StateLoader by validatedTransactions { companion object { @JvmStatic @@ -78,22 +78,22 @@ open class MockServices private constructor( /** * Makes database and mock services appropriate for unit tests. - * @param keys a list of [KeyPair] instances to be used by [MockServices]. + * @param moreKeys a list of additional [KeyPair] instances to be used by [MockServices]. * @param identityService an instance of [IdentityServiceInternal], see [makeTestIdentityService]. - * @param initialIdentityName the name of the first (typically sole) identity the services will represent. + * @param initialIdentity the first (typically sole) identity the services will represent. * @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices]. */ @JvmStatic - fun makeTestDatabaseAndMockServices(keys: List, + fun makeTestDatabaseAndMockServices(cordappPackages: List, identityService: IdentityServiceInternal, - cordappPackages: List = emptyList(), - initialIdentityName: CordaX500Name): Pair { + initialIdentity: TestIdentity, + vararg moreKeys: KeyPair): Pair { val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) val dataSourceProps = makeTestDataSourceProperties() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService, schemaService) val mockService = database.transaction { - object : MockServices(cordappLoader, identityService, initialIdentityName, keys.toTypedArray()) { + object : MockServices(cordappLoader, identityService, initialIdentity, moreKeys) { override val vaultService: VaultServiceInternal = makeVaultService(database.hibernateConfig, schemaService) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { @@ -109,13 +109,12 @@ open class MockServices private constructor( } } - private constructor(cordappLoader: CordappLoader, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, keys: Array) : this(cordappLoader, MockTransactionStorage(), identityService, initialIdentityName, keys) - constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, vararg keys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentityName, keys) - constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity.name, arrayOf(initialIdentity.keyPair) + moreKeys) - constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, vararg keys: KeyPair) : this(emptyList(), identityService, initialIdentityName, *keys) - constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name) : this(identityService, initialIdentityName, generateKeyPair()) - - val key: KeyPair get() = keys.first() + private constructor(cordappLoader: CordappLoader, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, moreKeys: Array) : this(cordappLoader, MockTransactionStorage(), identityService, initialIdentity, moreKeys) + constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity, moreKeys) + constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity(initialIdentityName, key), *moreKeys) + constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name) : this(cordappPackages, identityService, TestIdentity(initialIdentityName)) + constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(emptyList(), identityService, TestIdentity(initialIdentityName, key), *moreKeys) + constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name) : this(emptyList(), identityService, TestIdentity(initialIdentityName)) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { txs.forEach { @@ -124,16 +123,14 @@ open class MockServices private constructor( } final override val attachments = MockAttachmentStorage() - override val keyManagementService: KeyManagementService by lazy { MockKeyManagementService(identityService, *keys) } - + override val keyManagementService: KeyManagementService by lazy { MockKeyManagementService(identityService, *arrayOf(initialIdentity.keyPair) + moreKeys) } override val vaultService: VaultService get() = throw UnsupportedOperationException() override val contractUpgradeService: ContractUpgradeService get() = throw UnsupportedOperationException() override val networkMapCache: NetworkMapCache get() = throw UnsupportedOperationException() override val clock: Clock get() = Clock.systemUTC() override val myInfo: NodeInfo get() { - val identity = getTestPartyAndCertificate(initialIdentityName, key.public) - return NodeInfo(emptyList(), listOf(identity), 1, serial = 1L) + return NodeInfo(emptyList(), listOf(initialIdentity.identity), 1, serial = 1L) } override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) val mockCordappProvider = MockCordappProvider(cordappLoader, attachments) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index e0100da05d..b11dfc1a67 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -118,8 +118,10 @@ fun getTestPartyAndCertificate(name: CordaX500Name, publicKey: PublicKey): Party return getTestPartyAndCertificate(Party(name, publicKey)) } -class TestIdentity @JvmOverloads constructor(val name: CordaX500Name, entropy: Long? = null) { - val keyPair: KeyPair = if (entropy != null) entropyToKeyPair(BigInteger.valueOf(entropy)) else generateKeyPair() +class TestIdentity(val name: CordaX500Name, val keyPair: KeyPair) { + constructor(name: CordaX500Name, entropy: Long) : this(name, entropyToKeyPair(BigInteger.valueOf(entropy))) + constructor(name: CordaX500Name) : this(name, generateKeyPair()) + val publicKey: PublicKey get() = keyPair.public val party: Party = Party(name, publicKey) val identity: PartyAndCertificate by lazy { getTestPartyAndCertificate(party) } // Often not needed. From 02ad2b8b60e0be85a35dbf7ff4d10a287e33b3fc Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Fri, 15 Dec 2017 16:53:57 +0000 Subject: [PATCH 24/44] Fix LargeTransactionTest (#2265) --- .../net/corda/nodeapi/internal/NetworkParametersGenerator.kt | 2 +- .../kotlin/net/corda/testing/node/network/NetworkMapServer.kt | 2 +- .../net/corda/testing/common/internal/ParametersUtilities.kt | 2 +- .../src/main/kotlin/net/corda/testing/TestConstants.kt | 2 +- .../src/main/kotlin/net/corda/demobench/model/NodeController.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) 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 index fd0bcbda20..c903938b22 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt @@ -41,7 +41,7 @@ class NetworkParametersGenerator { minimumPlatformVersion = 1, notaries = notaryInfos, modifiedTime = Instant.now(), - maxMessageSize = 40000, + maxMessageSize = 10485760, maxTransactionSize = 40000, epoch = 1 )) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt index 70abb19281..b9ca504279 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt @@ -38,7 +38,7 @@ class NetworkMapServer(cacheTimeout: Duration, root_ca: CertificateAndKeyPair = ROOT_CA, // Default to ROOT_CA for testing. vararg additionalServices: Any) : Closeable { companion object { - val stubNetworkParameter = NetworkParameters(1, emptyList(), 40000, 40000, Instant.now(), 10) + val stubNetworkParameter = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10) private val serializedParameters = stubNetworkParameter.serialize() private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair { diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index 13ca04f95c..938bf66976 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -8,7 +8,7 @@ fun testNetworkParameters( notaries: List, minimumPlatformVersion: Int = 1, modifiedTime: Instant = Instant.now(), - maxMessageSize: Int = 1048576, + maxMessageSize: Int = 10485760, maxTransactionSize: Int = 40000, epoch: Int = 1 ): NetworkParameters { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index cf147d6c90..27c52eb1bc 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -53,4 +53,4 @@ fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) object DummyCommandData : TypeOnlyCommandData() /** Maximum artemis message size. 10 MiB maximum allowed file size for attachments, including message headers. */ -const val MAX_MESSAGE_SIZE: Int = 1048576 +const val MAX_MESSAGE_SIZE: Int = 10485760 diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index 834b8a0ae3..81ddfe8881 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -143,7 +143,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { minimumPlatformVersion = 1, notaries = listOf(NotaryInfo(identity, config.nodeConfig.notary!!.validating)), modifiedTime = Instant.now(), - maxMessageSize = 40000, + maxMessageSize = 10485760, maxTransactionSize = 40000, epoch = 1 )) From 595d41af0407ffa7f4e98e72a4789b68f0f7d284 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Fri, 15 Dec 2017 17:48:33 +0000 Subject: [PATCH 25/44] AMQP Bridging between nodes (#2181) * Able to send hand coded messages to an Artemis node inbox Get startup race condition fixed. Start cleanup work. Fixup after rebase Remove SASL hack for now Minor tweaks. Enable AMQP mode manually. Add configuration control Slight clean up Stop timeouts that don't work with AMQP Rename class Get TLS constants from :node-api Primitive integration test Put back commented line Session per bridge to alow rollback on remote rejects. Add more tests and handle multiple IP adddresses Reduce logging Fixup after rebase Add a test to verify the remote end AMQP rejection logic works and does cause message replay. Allow Artemis to duplicate after session rollback Reduce number of threads Move legacy bridge related code over to CoreBridgeManager Shared threadpool for bridges Add a test to confirm that no side effects when using a shared thread pool. Address PR comments and remove dead lines Rebase and add some comments Remove a couple of blank lines Ensure AMQP bridges are used in tests Fixup after removal of testNodeConfiguration Add a couple of doc comments Add a couple of doc comments Make things internal and use CordaFuture Address some PR comments Change comment type * Use Artemis 2.2 to fix AMQP problems. Add explicit test of legacy core bridges, as marking the factory class private had silently broken them. * Fix change due to using Artemis 2.2 --- build.gradle | 5 +- .../corda/client/rpc/RPCConcurrencyTests.kt | 4 +- docs/source/changelog.rst | 3 + docs/source/corda-configuration-file.rst | 5 +- .../net/corda/nodeapi/ArtemisTcpTransport.kt | 6 +- .../net/corda/node/amqp/AMQPBridgeTest.kt | 241 +++++++++ .../net/corda/node/amqp/ProtonWrapperTests.kt | 308 +++++++++++ .../services/messaging/P2PMessagingTest.kt | 5 +- .../engine/ConnectionStateMachine.kt | 483 ++++++++++++++++++ .../protonwrapper/engine/EventProcessor.kt | 136 +++++ .../protonwrapper/engine/NettyWritable.kt | 63 +++ .../messages/ApplicationMessage.kt | 14 + .../protonwrapper/messages/MessageStatus.kt | 11 + .../protonwrapper/messages/ReceivedMessage.kt | 13 + .../protonwrapper/messages/SendableMessage.kt | 10 + .../messages/impl/ReceivedMessageImpl.kt | 30 ++ .../messages/impl/SendableMessageImpl.kt | 37 ++ .../protonwrapper/netty/AMQPChannelHandler.kt | 156 ++++++ .../protonwrapper/netty/AMQPClient.kt | 194 +++++++ .../protonwrapper/netty/AMQPServer.kt | 187 +++++++ .../protonwrapper/netty/ConnectionChange.kt | 6 + .../internal/protonwrapper/netty/SSLHelper.kt | 39 ++ .../node/services/config/NodeConfiguration.kt | 8 +- .../services/messaging/AMQPBridgeManager.kt | 203 ++++++++ .../messaging/ArtemisMessagingClient.kt | 9 +- .../messaging/ArtemisMessagingServer.kt | 159 +----- .../node/services/messaging/BridgeManager.kt | 20 + .../services/messaging/CoreBridgeManager.kt | 166 ++++++ node/src/main/resources/reference.conf | 1 + .../messaging/ArtemisMessagingTests.kt | 5 +- .../kotlin/net/corda/testing/node/MockNode.kt | 11 +- .../corda/testing/node/internal/RPCDriver.kt | 8 +- 32 files changed, 2386 insertions(+), 160 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt create mode 100644 node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/ConnectionStateMachine.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/EventProcessor.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/engine/NettyWritable.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ApplicationMessage.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/MessageStatus.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/ReceivedMessage.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/SendableMessage.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/messages/impl/SendableMessageImpl.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPClient.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPServer.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/SSLHelper.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/BridgeManager.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt diff --git a/build.gradle b/build.gradle index 490068e188..e416f5a248 100644 --- a/build.gradle +++ b/build.gradle @@ -25,8 +25,7 @@ buildscript { * 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.1.0 (version used prior to the 2.4 - * bump). + * 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 @@ -35,7 +34,7 @@ buildscript { * The issue has been reported to upstream: * https://issues.apache.org/jira/browse/ARTEMIS-1559 */ - ext.artemis_version = '2.1.0' + 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/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt index b3975f3516..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,10 +9,10 @@ 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.ConcurrentHashSet +import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet import org.junit.After import org.junit.Test import org.junit.runner.RunWith diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 5951fdfbb3..6dfb168cc2 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -127,6 +127,9 @@ UNRELEASED * 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. +* 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. + .. _changelog_v1: Release 1.0 diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 566e3d5e64..49572d1c8e 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -172,4 +172,7 @@ path to the node's base directory. :port: The port to start SSH server on :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/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/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..1e094f4194 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -0,0 +1,241 @@ +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 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..3f6c8444f7 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -0,0 +1,308 @@ +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 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/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index e87ff6e48b..7d8bf110e1 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 @@ -99,7 +99,7 @@ class P2PMessagingTest { } // Wait until the first request is received - crashingNodes.firstRequestReceived.await(5, TimeUnit.SECONDS) + crashingNodes.firstRequestReceived.await() // Stop alice's node after we ensured that the first request was delivered and ignored. alice.dispose() val numberOfRequestsReceived = crashingNodes.requestsReceived.get() @@ -109,8 +109,7 @@ class P2PMessagingTest { // Restart the node and expect a response val aliceRestarted = startAlice() - val response = aliceRestarted.network.onNext(dummyTopic, sessionId).getOrThrow(5.seconds) - + val response = aliceRestarted.network.onNext(dummyTopic, sessionId).getOrThrow() assertThat(crashingNodes.requestsReceived.get()).isGreaterThan(numberOfRequestsReceived) assertThat(response).isEqualTo(responseMessage) } 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/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 7d47ea60cb..60acff95d1 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.* @@ -42,6 +42,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val detectPublicIp: Boolean get() = true val sshd: SSHDConfiguration? val database: DatabaseConfig + val useAMQPBridges: Boolean get() = true } data class DevModeOptions(val disableCheckpointChecker: Boolean = false) @@ -116,7 +117,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(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode) + override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode), + override val useAMQPBridges: Boolean = true ) : NodeConfiguration { override val exportJMXto: String get() = "http" 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 9a160138c1..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,12 +3,15 @@ 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, private val maxMessageSize: Int) { companion object { @@ -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 4b80354fd2..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. @@ -114,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() @@ -133,6 +128,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, } fun stop() = mutex.locked { + bridgeManager.close() networkChangeHandle?.unsubscribe() networkChangeHandle = null activeMQServer.stop() @@ -153,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()) @@ -212,11 +215,13 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, 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 { @@ -299,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)) { @@ -334,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/resources/reference.conf b/node/src/main/resources/reference.conf index 0eb49880ea..fe29913bca 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -25,3 +25,4 @@ activeMQServer = { maxRetryIntervalMin = 3 } } +useAMQPBridges = true \ No newline at end of file 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 e4136ffc24..def1012235 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 @@ -5,16 +5,16 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.context.AuthServiceId import net.corda.core.crypto.generateKeyPair 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.node.services.config.CertChainPolicyConfig import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.* @@ -72,6 +72,7 @@ class ArtemisMessagingTests { 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()) 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 c3c510b0b7..f9c699b901 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 @@ -27,8 +27,8 @@ import net.corda.core.utilities.seconds 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.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 @@ -37,14 +37,14 @@ 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.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.config.User +import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.DUMMY_NOTARY_NAME +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties @@ -495,5 +495,6 @@ private fun mockNodeConfiguration(): NodeConfiguration { doReturn(5).whenever(it).messageRedeliveryDelaySeconds doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec doReturn(null).whenever(it).devModeOptions + doReturn(true).whenever(it).useAMQPBridges } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index 72a5bc8143..a5dff915ca 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -17,7 +17,6 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.RPCOps import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.internal.security.RPCSecurityManagerImpl -import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.RPCServer import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.nodeapi.ArtemisTcpTransport @@ -47,13 +46,12 @@ import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl 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.protocol.RemotingConnection +import org.apache.activemq.artemis.spi.core.remoting.Connection import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3 import java.lang.reflect.Method import java.nio.file.Path import java.nio.file.Paths import java.util.* -import javax.security.cert.X509Certificate inline fun RPCDriverDSL.startInVmRpcClient( username: String = rpcTestUser.username, @@ -135,11 +133,11 @@ fun rpcDriver( private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityManager3 { override fun validateUser(user: String?, password: String?) = isValid(user, password) override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?) = isValid(user, password) - override fun validateUser(user: String?, password: String?, certificates: Array?): String? { + override fun validateUser(user: String?, password: String?, connection: Connection?): String? { return validate(user, password) } - override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?, address: String?, connection: RemotingConnection?): String? { + override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?, address: String?, connection: Connection?): String? { return validate(user, password) } From 2652ae111a0d9c1397f6a0a436c3f8e75f7adafd Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 15 Dec 2017 19:18:31 +0000 Subject: [PATCH 26/44] CORDA-716 Devrel feedback (#2266) * * Document TestIdentity entropy and enforce that it actually works * Ledger/transaction DSL default notary with fresh key * MockServices default identity with fresh key * makeTestIdentityService now takes vararg * Require cordappPackages for MockServices * DSL automatic serialization init * Improve error when two MockNetworks used * * Make cordappPackages required by MockNetwork * Default identity service in MockServices * Make notarySpecs Java-friendly --- .../confidential/SwapIdentitiesFlowTests.kt | 2 +- .../net/corda/core/identity/CordaX500Name.kt | 6 ++- .../net/corda/core/flows/FlowsInJavaTest.java | 3 +- .../core/crypto/PartialMerkleTreeTest.kt | 2 +- .../net/corda/core/flows/AttachmentTests.kt | 2 +- .../corda/core/flows/ReceiveAllFlowTests.kt | 2 +- .../AttachmentSerializationTest.kt | 2 +- .../TransactionSerializationTests.kt | 2 +- .../LedgerTransactionQueryTests.kt | 2 +- .../TransactionEncumbranceTests.kt | 2 +- .../tutorial/testdsl/CommercialPaperTest.java | 39 ++++++------------- .../mocknetwork/TutorialMockNetwork.kt | 2 +- .../docs/tutorial/testdsl/TutorialTestDSL.kt | 2 +- .../contracts/asset/CashTestsJava.java | 3 +- .../finance/contracts/CommercialPaperTests.kt | 8 ++-- .../finance/contracts/asset/CashTests.kt | 4 +- .../contracts/asset/ObligationTests.kt | 4 +- .../ContractAttachmentSerializerTest.kt | 2 +- .../node/services/BFTNotaryServiceTests.kt | 2 +- .../identity/InMemoryIdentityService.kt | 2 +- .../services/vault/VaultQueryJavaTests.java | 2 +- .../net/corda/node/InteractiveShellTest.kt | 2 +- .../node/internal/NetworkParametersTest.kt | 1 + .../node/messaging/InMemoryMessagingTests.kt | 2 +- .../identity/InMemoryIdentityServiceTests.kt | 2 +- .../services/network/NetworkMapCacheTest.kt | 2 +- .../services/vault/NodeVaultServiceTest.kt | 6 +-- .../node/services/vault/VaultQueryTests.kt | 2 +- .../node/services/vault/VaultWithCashTest.kt | 2 +- .../kotlin/net/corda/irs/contract/IRSTests.kt | 2 +- .../corda/netmap/simulation/IRSSimulation.kt | 2 +- .../testing/node/FlowStackSnapshotTest.kt | 2 +- .../kotlin/net/corda/testing/node/MockNode.kt | 21 ++++++---- .../net/corda/testing/node/MockServices.kt | 18 ++++++--- .../net/corda/testing/node/NodeTestUtils.kt | 27 ++++++++++--- .../node/MockNodeFactoryInJavaTest.java | 10 +++-- .../kotlin/net/corda/testing/CoreTestUtils.kt | 20 ++++++++-- .../corda/testing/SerializationTestHelpers.kt | 27 +++++++++---- .../net/corda/testing/TestIdentityTests.kt | 27 +++++++++++++ .../net/corda/loadtest/tests/NotaryTest.kt | 2 +- 40 files changed, 170 insertions(+), 102 deletions(-) create mode 100644 testing/test-utils/src/test/kotlin/net/corda/testing/TestIdentityTests.kt 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/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/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java index 2a536b7f0d..ae6431bec2 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -13,13 +13,14 @@ 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 org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.Assert.fail; import static net.corda.testing.node.NodeTestUtils.startFlow; 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; 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 4449f6a6c3..4b3f5b7f48 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -51,7 +51,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/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/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 736b38891f..838fd196ae 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -106,7 +106,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() 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..2d9c80bd44 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt @@ -28,7 +28,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 37af722e3c..1bdaa030ed 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -61,7 +61,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/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 0a361a7b95..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(ALICE_NAME, 70L); private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic(); 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(DUMMY_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.getPublicKey()); - doReturn(null).when(identityService).partyFromKey(BIG_CORP_PUBKEY); - doReturn(null).when(identityService).partyFromKey(ALICE.getPublicKey()); - ledgerServices = new MockServices(identityService, MEGA_CORP.getName()); - } + private final MockServices ledgerServices = new MockServices(emptyList(), makeTestIdentityService(MEGA_CORP.getIdentity())); // DOCSTART 1 private ICommercialPaperState getPaper() { @@ -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,7 +61,7 @@ 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.getPublicKey(), new JavaCommercialPaper.Commands.Move()); @@ -92,7 +77,7 @@ 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.getPublicKey(), new JavaCommercialPaper.Commands.Move()); @@ -108,7 +93,7 @@ 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.getPublicKey(), new JavaCommercialPaper.Commands.Move()); @@ -125,7 +110,7 @@ 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); @@ -146,7 +131,7 @@ 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 -> { @@ -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())); @@ -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())); @@ -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())); 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 4c4b5d76a4..0a71750b11 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 @@ -65,7 +65,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 45bd12a23f..82dc28b8c6 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 @@ -36,7 +36,7 @@ class CommercialPaperTest { @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/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java index 7bde2efedd..6702c92e4b 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,6 +12,7 @@ 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; @@ -37,7 +38,7 @@ public class CashTestsJava { IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class); doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPublicKey()); doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPublicKey()); - transaction(new MockServices(identityService, MEGA_CORP.getName()), DUMMY_NOTARY, tx -> { + transaction(new MockServices(emptyList(), identityService, MEGA_CORP.getName()), DUMMY_NOTARY, tx -> { tx.attachment(Cash.PROGRAM_ID); tx.input(Cash.PROGRAM_ID, inState); 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 a069b46537..14c4d2798f 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -120,7 +120,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,14 +260,14 @@ 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.keyPair) + private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair) private val issuerServices = MockServices(listOf("net.corda.finance.contracts"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.keyPair) private lateinit var moveTX: SignedTransaction @Test fun `issue move and then redeem`() { val aliceDatabaseAndServices = makeTestDatabaseAndMockServices( listOf("net.corda.finance.contracts"), - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), + 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 @@ -279,7 +279,7 @@ class CommercialPaperTestsGeneric { } val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices( listOf("net.corda.finance.contracts"), - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), + 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 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 b81aba2395..946c8c1ea5 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 @@ -99,7 +99,7 @@ class CashTests { val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val databaseAndServices = makeTestDatabaseAndMockServices( listOf("net.corda.finance.contracts.asset"), - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), + 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 @@ -136,7 +136,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 1677955b79..f8baae1aeb 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 @@ -77,7 +77,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.keyPair) + 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 +86,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/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..341ee3a162 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 @@ -23,7 +23,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/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 1b1632a34c..6073713271 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 @@ -42,7 +42,7 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class BFTNotaryServiceTests { - private val mockNet = MockNetwork() + private val mockNet = MockNetwork(emptyList()) private lateinit var notary: Party private lateinit var node: StartedNode 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/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index cf930962ea..f8fd0950e7 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 @@ -70,7 +70,7 @@ public class VaultQueryJavaTests { @Before public void setUp() throws CertificateException, InvalidAlgorithmParameterException { List cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.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( cordappPackages, identitySvc, diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index d2caa2f214..c94dc637ab 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -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 index 9bb49eee12..83bd00b96e 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt @@ -21,6 +21,7 @@ import org.assertj.core.api.Assertions.* class NetworkParametersTest { private val mockNet = MockNetwork( + emptyList(), MockNetworkParameters(networkSendManuallyPumped = true), notarySpecs = listOf(MockNetwork.NotarySpec(DUMMY_NOTARY_NAME))) 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 18ed176857..5959c32ff2 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -17,7 +17,7 @@ class InMemoryMessagingTests { @Before fun setUp() { - mockNet = MockNetwork() + mockNet = MockNetwork(emptyList()) } @After 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 21c9330758..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 @@ -32,7 +32,7 @@ class InMemoryIdentityServiceTests { val BOB get() = bob.party val BOB_IDENTITY get() = bob.identity val BOB_PUBKEY get() = bob.publicKey - fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities.toSet(), DEV_TRUST_ROOT) + fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_TRUST_ROOT) } @Rule 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/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 492067a7ba..3d08fef1e7 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 @@ -87,7 +87,7 @@ class NodeVaultServiceTest { LogHelper.setLevel(NodeVaultService::class) val databaseAndServices = MockServices.makeTestDatabaseAndMockServices( cordappPackages, - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), + makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), megaCorp) database = databaseAndServices.first services = databaseAndServices.second @@ -135,7 +135,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) { @@ -585,7 +585,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 670faaf85b..84ff1a5bed 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 @@ -105,7 +105,7 @@ class VaultQueryTests { // register additional identities val databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages, - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)), + makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity), megaCorp, DUMMY_NOTARY_KEY) database = databaseAndServices.first 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 2d2932aa01..b68d555d5b 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 @@ -70,7 +70,7 @@ class VaultWithCashTest { LogHelper.setLevel(VaultWithCashTest::class) val databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages, - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity)), + makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity), TestIdentity(MEGA_CORP.name, servicesKey), dummyNotary.keyPair) database = databaseAndServices.first 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 6615d5f7c0..4a55ce11f2 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 @@ -236,7 +236,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/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 a3c499dc92..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,7 +288,7 @@ 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() 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 f9c699b901..1bd6d87f76 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 @@ -73,13 +73,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) } /** @@ -124,16 +124,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. @@ -151,9 +152,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 /** diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index cddc41827b..bc3add840c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -44,7 +44,7 @@ import java.sql.Connection import java.time.Clock import java.util.* -fun makeTestIdentityService(identities: Iterable = emptySet()) = InMemoryIdentityService(identities, DEV_TRUST_ROOT) +fun makeTestIdentityService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_TRUST_ROOT) /** * A singleton utility that only provides a mock identity, key and storage service. However, this is sufficient for * building chains of transactions and verifying them. It isn't sufficient for testing flows however. @@ -110,11 +110,17 @@ open class MockServices private constructor( } private constructor(cordappLoader: CordappLoader, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, moreKeys: Array) : this(cordappLoader, MockTransactionStorage(), identityService, initialIdentity, moreKeys) - constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity, moreKeys) - constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity(initialIdentityName, key), *moreKeys) - constructor(cordappPackages: List, identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name) : this(cordappPackages, identityService, TestIdentity(initialIdentityName)) - constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(emptyList(), identityService, TestIdentity(initialIdentityName, key), *moreKeys) - constructor(identityService: IdentityServiceInternal, initialIdentityName: CordaX500Name) : this(emptyList(), identityService, TestIdentity(initialIdentityName)) + @JvmOverloads + constructor(cordappPackages: List, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity, moreKeys) + + @JvmOverloads + constructor(cordappPackages: List, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity(initialIdentityName, key), *moreKeys) + + @JvmOverloads + constructor(cordappPackages: List, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name) : this(cordappPackages, identityService, TestIdentity(initialIdentityName)) + + @JvmOverloads + constructor(cordappPackages: List, identityService: IdentityServiceInternal = makeTestIdentityService(), vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity.fresh("MockServices"), *moreKeys) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { txs.forEach { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index 9e3e9c7329..d54d025af7 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -11,6 +11,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine import net.corda.core.node.ServiceHub +import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices @@ -19,11 +20,24 @@ import net.corda.testing.* /** * Creates and tests a ledger built by the passed in dsl. */ +@JvmOverloads fun ServiceHub.ledger( - notary: Party, - dsl: LedgerDSL.() -> Unit + notary: Party = TestIdentity.fresh("ledger notary").party, + script: LedgerDSL.() -> Unit ): LedgerDSL { - return LedgerDSL(TestLedgerDSLInterpreter(this), notary).apply(dsl) + val serializationExists = try { + effectiveSerializationEnv + true + } catch (e: IllegalStateException) { + false + } + return LedgerDSL(TestLedgerDSLInterpreter(this), notary).apply { + if (serializationExists) { + script() + } else { + SerializationEnvironmentRule.run("ledger") { script() } + } + } } /** @@ -31,11 +45,12 @@ fun ServiceHub.ledger( * * @see LedgerDSLInterpreter._transaction */ +@JvmOverloads fun ServiceHub.transaction( - notary: Party, - dsl: TransactionDSL.() -> EnforceVerifyOrFail + notary: Party = TestIdentity.fresh("transaction notary").party, + script: TransactionDSL.() -> EnforceVerifyOrFail ) = ledger(notary) { - dsl(TransactionDSL(TestTransactionDSLInterpreter(interpreter, TransactionBuilder(notary)), notary)) + TransactionDSL(TestTransactionDSLInterpreter(interpreter, TransactionBuilder(notary)), notary).script() } fun testActor(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = Actor(Actor.Id("Only For Testing"), AuthServiceId("TEST"), owningLegalIdentity) diff --git a/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java b/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java index 4e6b9215ca..b6b336ec7b 100644 --- a/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java +++ b/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java @@ -2,6 +2,8 @@ package net.corda.testing.node; import org.jetbrains.annotations.NotNull; +import static java.util.Collections.emptyList; + @SuppressWarnings("unused") public class MockNodeFactoryInJavaTest { private static class CustomNode extends MockNetwork.MockNode { @@ -16,10 +18,10 @@ public class MockNodeFactoryInJavaTest { @SuppressWarnings("unused") private static void factoryIsEasyToPassInUsingJava() { //noinspection Convert2MethodRef - new MockNetwork(new MockNetworkParameters().setDefaultFactory(args -> new CustomNode(args))); - new MockNetwork(new MockNetworkParameters().setDefaultFactory(CustomNode::new)); + new MockNetwork(emptyList(), new MockNetworkParameters().setDefaultFactory(args -> new CustomNode(args))); + new MockNetwork(emptyList(), new MockNetworkParameters().setDefaultFactory(CustomNode::new)); //noinspection Convert2MethodRef - new MockNetwork().createNode(new MockNodeParameters(), args -> new CustomNode(args)); - new MockNetwork().createNode(new MockNodeParameters(), CustomNode::new); + new MockNetwork(emptyList()).createNode(new MockNodeParameters(), args -> new CustomNode(args)); + new MockNetwork(emptyList()).createNode(new MockNodeParameters(), CustomNode::new); } } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index b11dfc1a67..c68e470653 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -5,10 +5,7 @@ package net.corda.testing import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.StateRef -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.entropyToKeyPair -import net.corda.core.crypto.generateKeyPair +import net.corda.core.crypto.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate @@ -119,7 +116,22 @@ fun getTestPartyAndCertificate(name: CordaX500Name, publicKey: PublicKey): Party } class TestIdentity(val name: CordaX500Name, val keyPair: KeyPair) { + companion object { + /** + * Creates an identity that won't equal any other. This is mostly useful as a throwaway for test helpers. + * @param organisation the organisation part of the new identity's name. + */ + fun fresh(organisation: String): TestIdentity { + val keyPair = generateKeyPair() + val name = CordaX500Name(organisation, keyPair.public.toStringShort(), CordaX500Name.unspecifiedCountry) + return TestIdentity(name, keyPair) + } + } + + /** Creates an identity with a deterministic [keyPair] i.e. same [entropy] same keyPair .*/ constructor(name: CordaX500Name, entropy: Long) : this(name, entropyToKeyPair(BigInteger.valueOf(entropy))) + + /** Creates an identity with the given name and a fresh keyPair. */ constructor(name: CordaX500Name) : this(name, generateKeyPair()) val publicKey: PublicKey get() = keyPair.public diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 675e82eda0..a0061db572 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -33,19 +33,30 @@ class SerializationEnvironmentRule(private val inheritable: Boolean = false) : T }.whenever(it).execute(any()) } } + + /** Do not call, instead use [SerializationEnvironmentRule] as a [org.junit.Rule]. */ + fun run(taskLabel: String, task: (SerializationEnvironment) -> T): T { + return SerializationEnvironmentRule().apply { init(taskLabel) }.runTask(task) + } } lateinit var env: SerializationEnvironment override fun apply(base: Statement, description: Description): Statement { - env = createTestSerializationEnv(description.toString()) + init(description.toString()) return object : Statement() { - override fun evaluate() { - try { - env.asContextEnv(inheritable) { base.evaluate() } - } finally { - inVMExecutors.remove(env) - } - } + override fun evaluate() = runTask { base.evaluate() } + } + } + + private fun init(envLabel: String) { + env = createTestSerializationEnv(envLabel) + } + + private fun runTask(task: (SerializationEnvironment) -> T): T { + try { + return env.asContextEnv(inheritable, task) + } finally { + inVMExecutors.remove(env) } } } diff --git a/testing/test-utils/src/test/kotlin/net/corda/testing/TestIdentityTests.kt b/testing/test-utils/src/test/kotlin/net/corda/testing/TestIdentityTests.kt new file mode 100644 index 0000000000..c5a4e673bd --- /dev/null +++ b/testing/test-utils/src/test/kotlin/net/corda/testing/TestIdentityTests.kt @@ -0,0 +1,27 @@ +package net.corda.testing + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class TestIdentityTests { + @Test + fun `entropy works`() { + val a = TestIdentity(ALICE_NAME, 123) + val b = TestIdentity(BOB_NAME, 123) + assertEquals(a.publicKey, b.publicKey) + assertEquals(a.keyPair.private, b.keyPair.private) + } + + @Test + fun `fresh works`() { + val x = TestIdentity.fresh("xx") + val y = TestIdentity.fresh("yy") + // The param is called organisation so we'd better use it as such: + assertEquals("xx", x.name.organisation) + assertEquals("yy", y.name.organisation) + // A fresh identity shouldn't be equal to anything by accident: + assertNotEquals(x.name, y.name) + assertNotEquals(x.publicKey, y.publicKey) + } +} diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt index f54970ff69..5e2031b098 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt @@ -27,7 +27,7 @@ data class NotariseCommand(val issueTx: SignedTransaction, val moveTx: SignedTra val dummyNotarisationTest = LoadTest( "Notarising dummy transactions", generate = { _, _ -> - val issuerServices = MockServices(makeTestIdentityService(listOf(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity)), megaCorp.name, dummyCashIssuer.keyPair) + val issuerServices = MockServices(emptyList(), makeTestIdentityService(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity), megaCorp.name, dummyCashIssuer.keyPair) val generateTx = Generator.pickOne(simpleNodes).flatMap { node -> Generator.int().map { val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice From 8114a20abdd5e2683ef04515552ad284bf032978 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Sun, 17 Dec 2017 18:44:35 +0000 Subject: [PATCH 27/44] CORDA-716 Move non-API things to internal (#2256) --- .../client/jackson/JacksonSupportTest.kt | 1 + .../kotlin/net/corda/client/rpc}/Measure.kt | 2 +- .../corda/client/rpc/RPCPerformanceTests.kt | 1 - .../core/concurrent/ConcurrencyUtilsTest.kt | 2 +- .../core/contracts/DummyContractV2Tests.kt | 1 + .../corda/core/crypto/CompositeKeyTests.kt | 2 +- .../core/crypto/PartialMerkleTreeTest.kt | 4 ++ .../core/flows/CollectSignaturesFlowTests.kt | 1 + .../concurrent/CordaFutureImplTest.kt | 2 +- .../TransactionSerializationTests.kt | 1 + .../LedgerTransactionQueryTests.kt | 1 + .../TransactionEncumbranceTests.kt | 1 + .../core/transactions/TransactionTests.kt | 1 + .../docs/tutorial/testdsl/TutorialTestDSL.kt | 1 + .../corda/finance/contracts/universal/Cap.kt | 4 ++ .../contracts/asset/CashTestsJava.java | 2 +- .../finance/contracts/CommercialPaperTests.kt | 6 ++- .../finance/contracts/asset/CashTests.kt | 7 ++- .../contracts/asset/ObligationTests.kt | 2 + .../kotlin/net/corda/nodeapi}/Eventually.kt | 2 +- .../corda/nodeapi/NodeInfoFilesCopierTest.kt | 1 - ...tachmentsClassLoaderStaticContractTests.kt | 1 + .../internal/AttachmentsClassLoaderTests.kt | 2 + .../ContractAttachmentSerializerTest.kt | 1 + .../serialization/CordaClassResolverTests.kt | 2 +- .../serialization/ListsSerializationTest.kt | 4 +- .../serialization/MapsSerializationTest.kt | 4 +- .../serialization/SerializationTokenTest.kt | 2 +- .../serialization/SetsSerializationTest.kt | 2 +- .../net/corda/node/amqp/AMQPBridgeTest.kt | 1 + .../net/corda/node/amqp/ProtonWrapperTests.kt | 1 + .../node/services/AttachmentLoadingTests.kt | 2 +- .../node/services/network/NetworkMapTest.kt | 2 +- .../registration/NodeRegistrationTest.kt | 2 +- .../messaging/MQSecurityAsNodeTest.kt | 1 - .../services/messaging/MQSecurityAsRPCTest.kt | 1 - .../services/messaging/MQSecurityTest.kt | 1 - .../services}/messaging/SimpleMQClient.kt | 2 +- .../services/vault/VaultQueryJavaTests.java | 8 ++-- .../net/corda/node/InteractiveShellTest.kt | 2 +- .../node/messaging/InMemoryMessagingTests.kt | 4 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 7 ++- .../events/NodeSchedulerServiceTest.kt | 8 +++- .../messaging/ArtemisMessagingTests.kt | 2 + .../services/network/NetworkMapClientTest.kt | 2 +- .../persistence/DBCheckpointStorageTests.kt | 4 +- .../persistence/DBTransactionStorageTests.kt | 2 + .../persistence/HibernateConfigurationTest.kt | 9 ++-- .../persistence/NodeAttachmentStorageTest.kt | 4 +- .../services/schema/HibernateObserverTests.kt | 4 +- .../services/schema/NodeSchemaServiceTest.kt | 2 +- .../statemachine/FlowFrameworkTests.kt | 1 + .../DistributedImmutableMapTests.kt | 4 +- .../PersistentUniquenessProviderTests.kt | 2 + .../services/vault/NodeVaultServiceTest.kt | 4 +- .../node/services/vault/VaultQueryTests.kt | 7 ++- .../vault/VaultSoftLockManagerTest.kt | 2 +- .../node/services/vault/VaultWithCashTest.kt | 6 ++- .../corda/node/utilities/ObservablesTests.kt | 2 +- .../NetworkRegistrationHelperTest.kt | 2 +- .../corda/irs/api/NodeInterestRatesTest.kt | 2 + .../kotlin/net/corda/irs/contract/IRSTests.kt | 2 + .../netmap/simulation/IRSSimulationTest.kt | 2 +- .../corda/traderdemo/TraderDemoClientApi.kt | 2 +- .../traderdemo/TransactionGraphSearchTests.kt | 1 + .../testing/node/InMemoryMessagingNetwork.kt | 15 +++---- .../corda/testing/node/MockNetworkMapCache.kt | 39 ---------------- .../kotlin/net/corda/testing/node/MockNode.kt | 2 +- .../net/corda/testing/node/NodeTestUtils.kt | 1 + .../network/NetworkMapServer.kt | 2 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 44 +------------------ .../corda/testing/SerializationTestHelpers.kt | 1 + .../testing/TestDependencyInjectionBase.kt | 10 ----- .../kotlin/net/corda/testing/TestTimestamp.kt | 20 --------- .../testing/{ => dsl}/LedgerDSLInterpreter.kt | 2 +- .../net/corda/testing/{ => dsl}/TestDSL.kt | 4 +- .../{ => dsl}/TransactionDSLInterpreter.kt | 2 +- .../testing/internal/InternalTestUtils.kt | 44 +++++++++++++++++++ .../corda/testing/{ => internal}/LogHelper.kt | 2 +- .../vault}/DummyDealContract.kt | 5 +-- .../vault}/DummyDealStateSchemaV1.kt | 2 +- .../vault}/DummyLinearContract.kt | 6 +-- .../vault}/DummyLinearStateSchemaV1.kt | 2 +- .../vault}/DummyLinearStateSchemaV2.kt | 2 +- .../vault}/VaultFiller.kt | 4 +- .../net/corda/demobench/pty/ZeroFilterTest.kt | 2 +- 86 files changed, 185 insertions(+), 199 deletions(-) rename {testing/test-utils/src/main/kotlin/net/corda/testing => client/rpc/src/test/kotlin/net/corda/client/rpc}/Measure.kt (98%) rename {testing/test-utils/src/main/kotlin/net/corda/testing => node-api/src/test/kotlin/net/corda/nodeapi}/Eventually.kt (96%) rename {testing/test-utils/src/main/kotlin/net/corda/testing => node/src/integration-test/kotlin/net/corda/services}/messaging/SimpleMQClient.kt (98%) delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt rename testing/node-driver/src/main/kotlin/net/corda/testing/node/{ => internal}/network/NetworkMapServer.kt (99%) delete mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt delete mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/TestTimestamp.kt rename testing/test-utils/src/main/kotlin/net/corda/testing/{ => dsl}/LedgerDSLInterpreter.kt (99%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{ => dsl}/TestDSL.kt (99%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{ => dsl}/TransactionDSLInterpreter.kt (99%) create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt rename testing/test-utils/src/main/kotlin/net/corda/testing/{ => internal}/LogHelper.kt (98%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{contracts => internal/vault}/DummyDealContract.kt (90%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{schemas => internal/vault}/DummyDealStateSchemaV1.kt (96%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{contracts => internal/vault}/DummyLinearContract.kt (94%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{schemas => internal/vault}/DummyLinearStateSchemaV1.kt (97%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{schemas => internal/vault}/DummyLinearStateSchemaV2.kt (96%) rename testing/test-utils/src/main/kotlin/net/corda/testing/{contracts => internal/vault}/VaultFiller.kt (99%) 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 d5b311e9f4..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 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/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/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 4b3f5b7f48..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 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/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/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index 838fd196ae..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 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 2d9c80bd44..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 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 1bdaa030ed..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 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 9ef046072b..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 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 82dc28b8c6..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 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 6702c92e4b..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 @@ -16,7 +16,7 @@ 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.internal.InternalTestUtilsKt.rigorousMock; import static net.corda.testing.TestConstants.DUMMY_NOTARY_NAME; import static org.mockito.Mockito.doReturn; 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 14c4d2798f..dc011b5fa7 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 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 946c8c1ea5..af3900486e 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 @@ -20,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 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 f8baae1aeb..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 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/NodeInfoFilesCopierTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt index c14f2c854d..d2a18c7191 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt @@ -2,7 +2,6 @@ package net.corda.nodeapi import net.corda.cordform.CordformNode import net.corda.nodeapi.internal.NodeInfoFilesCopier -import net.corda.testing.eventually import org.junit.Before import org.junit.Rule import org.junit.Test 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/serialization/ContractAttachmentSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt index 341ee3a162..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 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/ListsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt index 5663a78beb..d732033477 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 f726d0b97a..d2ba294d2b 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.SessionData 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 243b73a803..9a910aeb3a 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,7 +6,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.statemachine.SessionData import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 -import net.corda.testing.kryoSpecific +import net.corda.testing.internal.kryoSpecific import net.corda.testing.SerializationEnvironmentRule import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals 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 index 1e094f4194..4c2f0ee881 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -17,6 +17,7 @@ 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 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 index 3f6c8444f7..871c7de400 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -21,6 +21,7 @@ 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 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 60e781e9c4..7fdc7a6d06 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 @@ -26,7 +26,7 @@ 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.rigorousMock +import net.corda.testing.internal.rigorousMock import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test 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 329e259714..251034a035 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 @@ -15,7 +15,7 @@ import net.corda.testing.BOB_NAME 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 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 a59809eb8e..83526ba759 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 @@ -18,7 +18,7 @@ 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.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.pkcs.PKCS10CertificationRequest 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 cdbb84c8e8..5ec0dca2c4 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 @@ -27,7 +27,6 @@ import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.testing.* import net.corda.testing.node.internal.NodeBasedTest -import net.corda.testing.messaging.SimpleMQClient import net.corda.testing.node.startFlow import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException import org.apache.activemq.artemis.api.core.ActiveMQSecurityException 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/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index f8fd0950e7..9ab2c4a2b4 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,8 +24,8 @@ 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 org.junit.After; import org.junit.Before; @@ -45,7 +45,7 @@ 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.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; @@ -69,7 +69,7 @@ public class VaultQueryJavaTests { @Before public void setUp() throws CertificateException, InvalidAlgorithmParameterException { - List cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); + List cordappPackages = Arrays.asList("net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); IdentityServiceInternal identitySvc = makeTestIdentityService(MEGA_CORP.getIdentity(), DUMMY_CASH_ISSUER_INFO.getIdentity(), DUMMY_NOTARY.getIdentity()); Pair databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages, diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index c94dc637ab..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 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 5959c32ff2..f049b040e0 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -1,8 +1,10 @@ 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.node.services.messaging.createMessage +import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before @@ -73,7 +75,7 @@ class InMemoryMessagingTests { var counter = 0 listOf(node1, node2, node3).forEach { it.network.addMessageHandler { _, _ -> 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 921637ba47..0af608e531 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/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 4ae3dc09ad..d2998cf641 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 @@ -11,6 +11,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 @@ -22,7 +23,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 @@ -30,11 +30,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 @@ -107,7 +109,9 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { 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/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index def1012235..5ddeddb6eb 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 @@ -18,6 +18,8 @@ import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor 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 org.assertj.core.api.Assertions.assertThat 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 23bed3ec85..0cfa199402 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 @@ -9,7 +9,7 @@ import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo import net.corda.testing.DEV_TRUST_ROOT import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation -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 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 41190a8355..e6326128bf 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt @@ -8,10 +8,10 @@ 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.SerializationEnvironmentRule 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.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 7defd484ab..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 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 2d144d4520..6b56a5d291 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,11 +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.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 @@ -92,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) 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 16fa18453a..f40fa4a6ba 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 @@ -15,11 +15,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 27a363864f..5a2a81913e 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 90714adc40..b5ba9320ca 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 @@ -31,6 +31,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 ff8a0780c1..7443507cb8 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 3d08fef1e7..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 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 84ff1a5bed..836939050e 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,11 +26,14 @@ 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.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.* 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 b68d555d5b..cac2e8db20 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,7 +21,9 @@ 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 @@ -39,7 +41,7 @@ import kotlin.test.fail class VaultWithCashTest { private companion object { - val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName) + val cordappPackages = listOf("net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1::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) 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 94f2c181ff..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,7 +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.internal.rigorousMock import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Before import org.junit.Rule 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 019b91c128..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 @@ -20,6 +20,8 @@ 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 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 4a55ce11f2..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 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/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 eff4a70136..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 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 427179a1ef..5d805a22b4 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 @@ -42,8 +42,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() { @@ -91,7 +91,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. @@ -150,9 +150,6 @@ class InMemoryMessagingNetwork( } } - - val everyoneOnline: AllPossibleRecipients = object : AllPossibleRecipients {} - fun stop() { val nodes = synchronized(this) { counter = -1 @@ -223,7 +220,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 -> { @@ -268,8 +265,8 @@ class InMemoryMessagingNetwork( private val peerHandle: PeerHandle, private val executor: AffinityExecutor, private val database: CordaPersistence) : SingletonSerializeAsToken(), MessagingService { - inner class Handler(val topicSession: TopicSession, - val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration + private inner class Handler(val topicSession: TopicSession, + val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : 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 1bd6d87f76..dde3cc1224 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 @@ -48,7 +48,7 @@ import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.rigorousMock +import net.corda.testing.internal.rigorousMock import net.corda.testing.setGlobalSerialization import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index d54d025af7..b4dd36315a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -16,6 +16,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* +import net.corda.testing.dsl.* /** * Creates and tests a ledger built by the passed in dsl. diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt similarity index 99% rename from testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt rename to testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index b9ca504279..9d88561798 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -1,4 +1,4 @@ -package net.corda.testing.node.network +package net.corda.testing.node.internal.network import net.corda.core.crypto.* import net.corda.core.internal.cert diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index c68e470653..f133a9557d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -13,26 +13,20 @@ import net.corda.core.internal.cert import net.corda.core.internal.x500Name import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.cert.X509CertificateHolder -import org.mockito.Mockito.mock -import org.mockito.internal.stubbing.answers.ThrowsException -import java.lang.reflect.Modifier import java.math.BigInteger import java.nio.file.Files import java.security.KeyPair import java.security.PublicKey -import java.util.* import java.util.concurrent.atomic.AtomicInteger /** @@ -80,7 +74,7 @@ fun freePort(): Int = freePortCounter.getAndAccumulate(0) { prev, _ -> 30000 + ( */ fun getFreeLocalPorts(hostName: String, numberToAlloc: Int): List { val freePort = freePortCounter.getAndAccumulate(0) { prev, _ -> 30000 + (prev - 30000 + numberToAlloc) % 10000 } - return (freePort until freePort + numberToAlloc).map { NetworkHostAndPort(hostName, it) } + return (0 until numberToAlloc).map { NetworkHostAndPort(hostName, freePort + it) } } fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration = object : SSLConfiguration { @@ -140,20 +134,6 @@ class TestIdentity(val name: CordaX500Name, val keyPair: KeyPair) { fun ref(vararg bytes: Byte): PartyAndReference = party.ref(*bytes) } -@Suppress("unused") -inline fun T.kryoSpecific(reason: String, function: () -> Unit) = if (!AMQP_ENABLED) { - function() -} else { - loggerFor().info("Ignoring Kryo specific test, reason: $reason") -} - -@Suppress("unused") -inline fun T.amqpSpecific(reason: String, function: () -> Unit) = if (AMQP_ENABLED) { - function() -} else { - loggerFor().info("Ignoring AMQP specific test, reason: $reason") -} - /** * Until we have proper handling of multiple identities per node, for tests we use the first identity as special one. * TODO: Should be removed after multiple identities are introduced. @@ -170,25 +150,3 @@ fun NodeInfo.singleIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe * Extract a single identity from the node info. Throws an error if the node has multiple identities. */ fun NodeInfo.singleIdentity(): Party = singleIdentityAndCert().party - -/** - * A method on a mock was called, but no behaviour was previously specified for that method. - * You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details. - */ -class UndefinedMockBehaviorException(message: String) : RuntimeException(message) - -inline fun rigorousMock() = rigorousMock(T::class.java) -/** - * Create a Mockito mock that has [UndefinedMockBehaviorException] as the default behaviour of all abstract methods, - * and [org.mockito.invocation.InvocationOnMock.callRealMethod] as the default for all concrete methods. - * @param T the type to mock. Note if you want concrete methods of a Kotlin interface to be invoked, - * it won't work unless you mock a (trivial) abstract implementation of that interface instead. - */ -fun rigorousMock(clazz: Class): T = mock(clazz) { - if (Modifier.isAbstract(it.method.modifiers)) { - // Use ThrowsException to hack the stack trace, and lazily so we can customise the message: - ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it. Args: ${Arrays.toString(it.arguments)}")).answer(it) - } else { - it.callRealMethod() - } -} diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index a0061db572..8aa5a0cc61 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -9,6 +9,7 @@ import net.corda.nodeapi.internal.serialization.* import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.testing.common.internal.asContextEnv +import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.testThreadFactory import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnector import org.junit.rules.TestRule diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt deleted file mode 100644 index 27a16a64f9..0000000000 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.corda.testing - -import org.junit.Rule - -@Deprecated("Instead of extending this class, use SerializationEnvironmentRule in the same way.") -abstract class TestDependencyInjectionBase { - @Rule - @JvmField - val testSerialization = SerializationEnvironmentRule() -} \ No newline at end of file diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestTimestamp.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestTimestamp.kt deleted file mode 100644 index f9cef533b0..0000000000 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestTimestamp.kt +++ /dev/null @@ -1,20 +0,0 @@ -package net.corda.testing - -import net.corda.testing.TestTimestamp.Companion.timestamp -import java.text.SimpleDateFormat -import java.util.* - -/** - * [timestamp] holds a formatted (UTC) timestamp that's set the first time it is queried. This is used to - * provide a uniform timestamp for tests. - */ -class TestTimestamp { - companion object { - val timestamp: String = { - val tz = TimeZone.getTimeZone("UTC") - val df = SimpleDateFormat("yyyyMMddHHmmss") - df.timeZone = tz - df.format(Date()) - }() - } -} diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/LedgerDSLInterpreter.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/LedgerDSLInterpreter.kt similarity index 99% rename from testing/test-utils/src/main/kotlin/net/corda/testing/LedgerDSLInterpreter.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/dsl/LedgerDSLInterpreter.kt index 47d3f68b62..869643d2aa 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/LedgerDSLInterpreter.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/LedgerDSLInterpreter.kt @@ -1,4 +1,4 @@ -package net.corda.testing +package net.corda.testing.dsl import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt similarity index 99% rename from testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt index 1fa551c6a1..bf48a07240 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt @@ -1,4 +1,4 @@ -package net.corda.testing +package net.corda.testing.dsl import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider @@ -12,9 +12,9 @@ import net.corda.core.node.ServicesForResolution import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction -import net.corda.testing.contracts.DummyContract import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.services.MockCordappProvider +import net.corda.testing.dummyCommand import java.io.InputStream import java.security.PublicKey import java.util.* diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt similarity index 99% rename from testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt index 668a53e2aa..b905e62462 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt @@ -1,4 +1,4 @@ -package net.corda.testing +package net.corda.testing.dsl import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt new file mode 100644 index 0000000000..fc65d3603c --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -0,0 +1,44 @@ +package net.corda.testing.internal + +import net.corda.core.utilities.loggerFor +import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED +import org.mockito.Mockito +import org.mockito.internal.stubbing.answers.ThrowsException +import java.lang.reflect.Modifier +import java.util.* + +@Suppress("unused") +inline fun T.kryoSpecific(reason: String, function: () -> Unit) = if (!AMQP_ENABLED) { + function() +} else { + loggerFor().info("Ignoring Kryo specific test, reason: $reason") +} + +@Suppress("unused") +inline fun T.amqpSpecific(reason: String, function: () -> Unit) = if (AMQP_ENABLED) { + function() +} else { + loggerFor().info("Ignoring AMQP specific test, reason: $reason") +} + +/** + * A method on a mock was called, but no behaviour was previously specified for that method. + * You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details. + */ +class UndefinedMockBehaviorException(message: String) : RuntimeException(message) + +inline fun rigorousMock() = rigorousMock(T::class.java) +/** + * Create a Mockito mock that has [UndefinedMockBehaviorException] as the default behaviour of all abstract methods, + * and [org.mockito.invocation.InvocationOnMock.callRealMethod] as the default for all concrete methods. + * @param T the type to mock. Note if you want concrete methods of a Kotlin interface to be invoked, + * it won't work unless you mock a (trivial) abstract implementation of that interface instead. + */ +fun rigorousMock(clazz: Class): T = Mockito.mock(clazz) { + if (Modifier.isAbstract(it.method.modifiers)) { + // Use ThrowsException to hack the stack trace, and lazily so we can customise the message: + ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it. Args: ${Arrays.toString(it.arguments)}")).answer(it) + } else { + it.callRealMethod() + } +} diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/LogHelper.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/LogHelper.kt similarity index 98% rename from testing/test-utils/src/main/kotlin/net/corda/testing/LogHelper.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/LogHelper.kt index 1fab8508fc..2a8a575bdd 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/LogHelper.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/LogHelper.kt @@ -1,4 +1,4 @@ -package net.corda.testing +package net.corda.testing.internal import net.corda.core.internal.packageName import org.apache.logging.log4j.Level diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyDealContract.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealContract.kt similarity index 90% rename from testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyDealContract.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealContract.kt index b19bfac076..453dac658c 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyDealContract.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealContract.kt @@ -1,4 +1,4 @@ -package net.corda.testing.contracts +package net.corda.testing.internal.vault import net.corda.core.contracts.Contract import net.corda.core.contracts.UniqueIdentifier @@ -10,9 +10,8 @@ import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.finance.contracts.DealState -import net.corda.testing.schemas.DummyDealStateSchemaV1 -val DUMMY_DEAL_PROGRAM_ID = "net.corda.testing.contracts.DummyDealContract" +val DUMMY_DEAL_PROGRAM_ID = "net.corda.testing.internal.vault.DummyDealContract" class DummyDealContract : Contract { override fun verify(tx: LedgerTransaction) {} diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyDealStateSchemaV1.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealStateSchemaV1.kt similarity index 96% rename from testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyDealStateSchemaV1.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealStateSchemaV1.kt index 33c6a44c8e..493e4688a5 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyDealStateSchemaV1.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealStateSchemaV1.kt @@ -1,4 +1,4 @@ -package net.corda.testing.schemas +package net.corda.testing.internal.vault import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.AbstractParty diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyLinearContract.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearContract.kt similarity index 94% rename from testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyLinearContract.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearContract.kt index 17737f4caa..0a2a962084 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyLinearContract.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearContract.kt @@ -1,4 +1,4 @@ -package net.corda.testing.contracts +package net.corda.testing.internal.vault import net.corda.core.contracts.Contract import net.corda.core.contracts.LinearState @@ -10,12 +10,10 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction -import net.corda.testing.schemas.DummyLinearStateSchemaV1 -import net.corda.testing.schemas.DummyLinearStateSchemaV2 import java.time.LocalDateTime import java.time.ZoneOffset.UTC -const val DUMMY_LINEAR_CONTRACT_PROGRAM_ID = "net.corda.testing.contracts.DummyLinearContract" +const val DUMMY_LINEAR_CONTRACT_PROGRAM_ID = "net.corda.testing.internal.vault.DummyLinearContract" class DummyLinearContract : Contract { override fun verify(tx: LedgerTransaction) { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyLinearStateSchemaV1.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearStateSchemaV1.kt similarity index 97% rename from testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyLinearStateSchemaV1.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearStateSchemaV1.kt index 1a610c9372..aa649337ff 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyLinearStateSchemaV1.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearStateSchemaV1.kt @@ -1,4 +1,4 @@ -package net.corda.testing.schemas +package net.corda.testing.internal.vault import net.corda.core.contracts.ContractState import net.corda.core.identity.AbstractParty diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyLinearStateSchemaV2.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearStateSchemaV2.kt similarity index 96% rename from testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyLinearStateSchemaV2.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearStateSchemaV2.kt index 35ad6880c5..224d6fed87 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/schemas/DummyLinearStateSchemaV2.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyLinearStateSchemaV2.kt @@ -1,4 +1,4 @@ -package net.corda.testing.schemas +package net.corda.testing.internal.vault import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.AbstractParty diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt similarity index 99% rename from testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt index 06e981c9b1..5febc72bd8 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt @@ -1,6 +1,4 @@ -@file:JvmName("VaultFiller") - -package net.corda.testing.contracts +package net.corda.testing.internal.vault import net.corda.core.contracts.* import net.corda.core.crypto.Crypto diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt index 41ccda83bf..0f50441e86 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt @@ -3,7 +3,7 @@ package net.corda.demobench.pty import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever -import net.corda.testing.rigorousMock +import net.corda.testing.internal.rigorousMock import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test From e9cead90556d2a7b0ff646108925a98aa64ce168 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 12 Dec 2017 22:07:20 +0000 Subject: [PATCH 28/44] CORDA-833: SignedNodeInfo object for holding a list of signatures, one for each identity in the NodeInfo. This forms part of the network map. --- .../net/corda/core/crypto/DigitalSignature.kt | 9 +- .../net/corda/nodeapi/internal/NetworkMap.kt | 10 +- .../internal/NetworkParametersGenerator.kt | 3 +- .../corda/nodeapi/internal/SignedNodeInfo.kt | 44 ++++ .../nodeapi/internal/SignedNodeInfoTest.kt | 80 ++++++ .../services/network/NodeInfoWatcherTest.kt | 44 ++-- .../net/corda/node/internal/AbstractNode.kt | 29 ++- .../node/services/network/NetworkMapClient.kt | 22 +- .../node/services/network/NodeInfoWatcher.kt | 10 +- .../services/network/NetworkMapClientTest.kt | 19 +- .../services/network/NetworkMapUpdaterTest.kt | 234 ++++++++---------- .../services/network/TestNodeInfoFactory.kt | 49 ---- .../node/internal/network/NetworkMapServer.kt | 11 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 23 +- .../testing/internal/TestNodeInfoBuilder.kt | 55 ++++ 15 files changed, 381 insertions(+), 261 deletions(-) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt delete mode 100644 node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt 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/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt index e29e0f6ca0..a9bf3ca97b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt @@ -4,6 +4,7 @@ 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 @@ -59,7 +60,7 @@ 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. * @@ -68,13 +69,14 @@ class SignedNetworkMap(val raw: SerializedBytes, val sig: DigitalSig */ @Throws(SignatureException::class, CertPathValidatorException::class) fun verified(trustedRoot: X509Certificate): NetworkMap { - sig.by.publicKey.verify(raw.bytes, sig) + signature.by.publicKey.verify(raw.bytes, signature) // Assume network map cert is under the default trust root. - X509Utilities.validateCertificateChain(trustedRoot, sig.by, trustedRoot) + 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/NetworkParametersGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt index c903938b22..6e0f5c4c40 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt @@ -1,7 +1,6 @@ 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 @@ -78,7 +77,7 @@ class NetworkParametersGenerator { 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-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/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/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 ba071c1fa1..cac6163282 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.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.keyPair) + 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/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 220bb62259..58432c2cd2 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.flows.* @@ -32,10 +34,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 @@ -59,6 +61,7 @@ import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.NetworkParameters +import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -173,6 +176,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 ...") @@ -184,11 +195,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) } } @@ -247,9 +258,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, 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() 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 5146c2bc53..d7a76cb008 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 @@ -15,7 +15,7 @@ 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.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. @@ -57,12 +57,8 @@ 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() } } @@ -100,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 @@ -138,9 +134,9 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, // 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 { @@ -163,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..c19f1a2195 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,7 +2,6 @@ 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 @@ -10,6 +9,7 @@ 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.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/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index 0cfa199402..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,10 +5,12 @@ 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.ALICE_NAME +import net.corda.testing.BOB_NAME import net.corda.testing.DEV_TRUST_ROOT import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation +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 @@ -23,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()) @@ -44,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) @@ -55,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() 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 ffee923ea9..98dbb96415 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,69 +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.NetworkMap +import net.corda.nodeapi.internal.SignedNodeInfo +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 { - companion object { - val NETWORK_PARAMS_HASH = SecureHash.randomSHA256() - } - @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, NETWORK_PARAMS_HASH) + 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) @@ -71,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(), NETWORK_PARAMS_HASH), 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, NETWORK_PARAMS_HASH) + 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(), NETWORK_PARAMS_HASH), 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, NETWORK_PARAMS_HASH) + 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, NETWORK_PARAMS_HASH) + 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/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 9d88561798..e704357863 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -7,10 +7,7 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.internal.DigitalSignatureWithCert -import net.corda.nodeapi.internal.NetworkMap -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.nodeapi.internal.SignedNetworkMap +import net.corda.nodeapi.internal.* import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities @@ -95,7 +92,7 @@ class NetworkMapServer(cacheTimeout: Duration, @Path("network-map") class InMemoryNetworkMapService(private val cacheTimeout: Duration, private val networkMapKeyAndCert: CertificateAndKeyPair) { - private val nodeInfoMap = mutableMapOf>() + private val nodeInfoMap = mutableMapOf() private val parametersHash = serializedParameters.hash private val signedParameters = SignedData( serializedParameters, @@ -106,7 +103,7 @@ class NetworkMapServer(cacheTimeout: Duration, @Path("publish") @Consumes(MediaType.APPLICATION_OCTET_STREAM) fun publishNodeInfo(input: InputStream): Response { - val registrationData = input.readBytes().deserialize>() + val registrationData = input.readBytes().deserialize() val nodeInfo = registrationData.verified() val nodeInfoHash = nodeInfo.serialize().sha256() nodeInfoMap.put(nodeInfoHash, registrationData) @@ -116,7 +113,7 @@ class NetworkMapServer(cacheTimeout: Duration, @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNetworkMap(): Response { - val networkMap = NetworkMap(nodeInfoMap.keys.map { it }, parametersHash) + val networkMap = NetworkMap(nodeInfoMap.keys.toList(), parametersHash) val serializedNetworkMap = networkMap.serialize() val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes) val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate.cert, signature)) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index f133a9557d..aa2dc08533 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -86,18 +86,29 @@ fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration = object : SSLC configureDevKeyAndTrustStores(legalName) } } + fun getTestPartyAndCertificate(party: Party): PartyAndCertificate { val trustRoot: X509CertificateHolder = DEV_TRUST_ROOT val intermediate: CertificateAndKeyPair = DEV_CA val nodeCaName = party.name.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN) - val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, party.name.x500Name))), arrayOf()) - val issuerKeyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256) - val issuerCertificate = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediate.certificate, intermediate.keyPair, nodeCaName, issuerKeyPair.public, - nameConstraints = nameConstraints) + val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val nodeCaCert = X509Utilities.createCertificate( + CertificateType.NODE_CA, + intermediate.certificate, + intermediate.keyPair, + nodeCaName, + nodeCaKeyPair.public, + nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, party.name.x500Name))), arrayOf())) - val certHolder = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, issuerCertificate, issuerKeyPair, party.name, party.owningKey) - val pathElements = listOf(certHolder, issuerCertificate, intermediate.certificate, trustRoot) + val identityCert = X509Utilities.createCertificate( + CertificateType.WELL_KNOWN_IDENTITY, + nodeCaCert, + nodeCaKeyPair, + party.name, + party.owningKey) + + val pathElements = listOf(identityCert, nodeCaCert, intermediate.certificate, trustRoot) val certPath = X509CertificateFactory().generateCertPath(pathElements.map(X509CertificateHolder::cert)) return PartyAndCertificate(certPath) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt new file mode 100644 index 0000000000..a367cff7e6 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt @@ -0,0 +1,55 @@ +package net.corda.testing.internal + +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.sign +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.SignedNodeInfo +import net.corda.testing.getTestPartyAndCertificate +import java.security.PrivateKey + +class TestNodeInfoBuilder { + private val identitiesAndPrivateKeys = ArrayList>() + + fun addIdentity(name: CordaX500Name): Pair { + val identityKeyPair = Crypto.generateKeyPair() + val identity = getTestPartyAndCertificate(name, identityKeyPair.public) + return Pair(identity, identityKeyPair.private).also { + identitiesAndPrivateKeys += it + } + } + + fun build(serial: Long = 1): NodeInfo { + return NodeInfo( + listOf(NetworkHostAndPort("my.${identitiesAndPrivateKeys[0].first.party.name.organisation}.com", 1234)), + identitiesAndPrivateKeys.map { it.first }, + 1, + serial + ) + } + + fun buildWithSigned(serial: Long = 1): Pair { + val nodeInfo = build(serial) + val privateKeys = identitiesAndPrivateKeys.map { it.second } + return Pair(nodeInfo, nodeInfo.signWith(privateKeys)) + } + + fun reset() { + identitiesAndPrivateKeys.clear() + } +} + +fun createNodeInfoAndSigned(vararg names: CordaX500Name, serial: Long = 1): Pair { + val nodeInfoBuilder = TestNodeInfoBuilder() + names.forEach { nodeInfoBuilder.addIdentity(it) } + return nodeInfoBuilder.buildWithSigned(serial) +} + +fun NodeInfo.signWith(keys: List): SignedNodeInfo { + val serialized = serialize() + val signatures = keys.map { it.sign(serialized.bytes) } + return SignedNodeInfo(serialized, signatures) +} From 00a5e3db6bef77e600839af658f35ffb5dd33dcf Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 7 Dec 2017 14:48:31 +0000 Subject: [PATCH 29/44] CORDA-830 Introducing the network bootstrapper Copying of the node-info files moved out of Cordform and into NetworkParametersGenerator (which is now called NetworkBootstrapper). This class becomes an external tool to enable deployment of nodes in a test setup on a single filesystem. --- .idea/compiler.xml | 2 + constants.properties | 2 +- docs/source/changelog.rst | 6 +- docs/source/network-map.rst | 4 +- docs/source/setting-up-a-corda-network.rst | 37 +++- .../main/kotlin/net/corda/plugins/Cordform.kt | 118 ++----------- .../src/main/kotlin/net/corda/plugins/Node.kt | 5 - .../internal/NetworkParametersCopier.kt | 31 ---- .../internal/NetworkParametersGenerator.kt | 107 ----------- .../internal/network/NetworkBootstrapper.kt | 167 ++++++++++++++++++ .../internal/{ => network}/NetworkMap.kt | 4 +- .../network/NetworkParametersCopier.kt | 35 ++++ .../{ => network}/NodeInfoFilesCopier.kt | 3 +- .../network}/NodeInfoFilesCopierTest.kt | 20 +-- .../node/services/BFTNotaryServiceTests.kt | 4 +- .../node/services/network/NetworkMapTest.kt | 21 +-- .../services/network/NodeInfoWatcherTest.kt | 2 +- .../net/corda/node/internal/AbstractNode.kt | 52 +++--- .../node/services/network/NetworkMapClient.kt | 6 +- .../node/services/network/NodeInfoWatcher.kt | 2 +- .../network/PersistentNetworkMapCache.kt | 2 +- .../node/internal/NetworkParametersTest.kt | 14 +- .../services/network/NetworkMapUpdaterTest.kt | 2 +- settings.gradle | 1 + .../kotlin/net/corda/testing/node/MockNode.kt | 14 +- .../testing/node/internal/DriverDSLImpl.kt | 10 +- .../testing/node/internal/NodeBasedTest.kt | 2 +- .../node/internal/network/NetworkMapServer.kt | 4 + .../net/corda/smoketesting/NodeProcess.kt | 2 +- .../common/internal/ParametersUtilities.kt | 4 +- tools/bootstrapper/build.gradle | 27 +++ .../model/DemoBenchNodeInfoFilesCopier.kt | 2 +- .../corda/demobench/model/NodeController.kt | 7 +- 33 files changed, 378 insertions(+), 341 deletions(-) delete mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt delete mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt rename node-api/src/main/kotlin/net/corda/nodeapi/internal/{ => network}/NetworkMap.kt (96%) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt rename node-api/src/main/kotlin/net/corda/nodeapi/internal/{ => network}/NodeInfoFilesCopier.kt (98%) rename node-api/src/test/kotlin/net/corda/nodeapi/{ => internal/network}/NodeInfoFilesCopierTest.kt (95%) create mode 100644 tools/bootstrapper/build.gradle diff --git a/.idea/compiler.xml b/.idea/compiler.xml index d5523e52b5..a33ce1e0f0 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -10,6 +10,8 @@ + + diff --git a/constants.properties b/constants.properties index a5b866aef0..5613b91279 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=3.0.1 +gradlePluginsVersion=3.0.2 kotlinVersion=1.1.60 platformVersion=2 guavaVersion=21.0 diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 6dfb168cc2..7d5422dee8 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,13 +6,11 @@ from the previous milestone release. UNRELEASED ---------- -* Support for external user credentials data source and password encryption [CORDA-827]. - * 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 sufffers from numerous disadvantages including lack of scalability, + 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. @@ -50,6 +48,8 @@ UNRELEASED * 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]. + * Exporting additional JMX metrics (artemis, hibernate statistics) and loading Jolokia agent at JVM startup when using DriverDSL and/or cordformation node runner. diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index 8ec565a95d..efe8110843 100644 --- a/docs/source/network-map.rst +++ b/docs/source/network-map.rst @@ -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. @@ -61,7 +61,7 @@ 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. 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/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 356da1179f..bd3842d3fa 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/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 954dac862a..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt +++ /dev/null @@ -1,31 +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 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_PARAMS_FILE_NAME) - } 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 6e0f5c4c40..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt +++ /dev/null @@ -1,107 +0,0 @@ -package net.corda.nodeapi.internal - -import com.typesafe.config.ConfigFactory -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.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(), - maxMessageSize = 10485760, - 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() { - _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/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 96% 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 a9bf3ca97b..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,4 +1,4 @@ -package net.corda.nodeapi.internal +package net.corda.nodeapi.internal.network import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate import java.time.Instant const val NETWORK_PARAMS_FILE_NAME = "network-parameters" -// TODO: Need more discussion on rather we should move this class out of internal. + /** * Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes. */ 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/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 95% 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 d2a18c7191..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,7 +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.nodeapi.eventually import org.junit.Before import org.junit.Rule import org.junit.Test @@ -14,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" @@ -33,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) } @@ -40,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/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 6073713271..ef762be725 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 @@ -25,9 +25,9 @@ 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.ServiceIdentityGenerator -import net.corda.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.testing.chooseIdentity -import net.corda.nodeapi.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand 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 251034a035..a46ae6f378 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,19 +1,20 @@ 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_PARAMS_FILE_NAME -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.testing.node.internal.CompatibilityZoneParams +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.SerializationEnvironmentRule import net.corda.testing.BOB_NAME +import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation +import net.corda.testing.node.internal.CompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat @@ -22,8 +23,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import java.net.URL -import java.nio.file.Files -import kotlin.streams.toList import kotlin.test.assertEquals class NetworkMapTest { @@ -51,10 +50,12 @@ class NetworkMapTest { @Test fun `node correctly downloads and saves network parameters file on startup`() { internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { - val aliceDir = baseDirectory(ALICE_NAME) - startNode(providedName = ALICE_NAME).getOrThrow() - val networkParameters = Files.list(aliceDir).toList().single { NETWORK_PARAMS_FILE_NAME == it.fileName.toString() } - .readAll().deserialize>().verified() + 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) } } 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 cac6163282..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 @@ -7,8 +7,8 @@ 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.nodeapi.internal.NodeInfoFilesCopier 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 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 58432c2cd2..ab435d9e58 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -59,10 +59,10 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor -import net.corda.nodeapi.internal.NETWORK_PARAMS_FILE_NAME -import net.corda.nodeapi.internal.NetworkParameters 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 @@ -73,7 +73,6 @@ import rx.Observable import rx.Scheduler import java.io.IOException import java.lang.reflect.InvocationTargetException -import java.nio.file.Files import java.security.KeyPair import java.security.KeyStoreException import java.security.PublicKey @@ -87,7 +86,6 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.SECONDS import kotlin.collections.set import kotlin.reflect.KClass -import kotlin.streams.toList import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair /** @@ -210,10 +208,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val identityService = makeIdentityService(identity.certificate) - networkMapClient = configuration.compatibilityZoneURL?.let { - NetworkMapClient(it, identityService.trustRoot) - } - readNetworkParameters() + 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) @@ -648,29 +644,31 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return PersistentKeyManagementService(identityService, keyPairs) } - private fun readNetworkParameters() { - val files = Files.list(configuration.baseDirectory).filter { NETWORK_PARAMS_FILE_NAME == it.fileName.toString() }.toList() - val paramsFromFile = try { - // It's fine at this point if we don't have network parameters or have corrupted file, later we check if parameters can be downloaded from network map server. - files[0].readAll().deserialize>().verified() - } catch (t: Exception) { - log.warn("Couldn't find correct network parameters file in the base directory") - null + private fun retrieveNetworkParameters() { + val networkParamsFile = configuration.baseDirectory.list { paths -> + paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().orElse(null) } - networkParameters = if (paramsFromFile != null) { - paramsFromFile - } else if (networkMapClient != null) { - log.info("Requesting network parameters from network map server...") - val (networkMap, _) = networkMapClient!!.getNetworkMap() - val signedParams = networkMapClient!!.getNetworkParameter(networkMap.networkParameterHash) ?: throw IllegalArgumentException("Failed loading network parameters from network map server") - val verifiedParams = signedParams.verified() // Verify before saving. + + 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 - } else { - throw IllegalArgumentException("Couldn't load network parameters file") } - log.info("Loaded network parameters $networkParameters") - check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node's platform version is lower than network's required minimumPlatformVersion" } + + 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/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index d7a76cb008..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,9 +12,9 @@ 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.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 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 c19f1a2195..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 @@ -8,7 +8,7 @@ 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 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 1d2e9d1a36..8cb6bd68b5 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 diff --git a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt index 83bd00b96e..0b9e42640c 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt @@ -7,17 +7,21 @@ 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.NetworkParameters -import net.corda.nodeapi.internal.NetworkParametersCopier -import net.corda.nodeapi.internal.NotaryInfo -import net.corda.testing.* +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 -import org.assertj.core.api.Assertions.* class NetworkParametersTest { private val mockNet = MockNetwork( 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 98dbb96415..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 @@ -16,8 +16,8 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.millis import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.nodeapi.internal.NetworkMap 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 diff --git a/settings.gradle b/settings.gradle index 14c2ffd469..a4f87dee28 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,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/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index dde3cc1224..2ed3d9b65d 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,8 +22,8 @@ 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.node.VersionInfo 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 @@ -37,18 +37,18 @@ 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.NetworkParametersCopier -import net.corda.nodeapi.internal.NotaryInfo import net.corda.nodeapi.internal.ServiceIdentityGenerator 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 -import net.corda.testing.internal.rigorousMock import net.corda.testing.setGlobalSerialization import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils @@ -229,12 +229,12 @@ class MockNetwork(private val cordappPackages: List, } 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) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 288c63ef4c..df1cb0ac03 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -35,7 +35,8 @@ import net.corda.node.services.transactions.RaftNonValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper -import net.corda.nodeapi.internal.* +import net.corda.nodeapi.internal.ServiceIdentityGenerator +import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.toConfig @@ -43,8 +44,13 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.addOrReplaceCertificate import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore import net.corda.nodeapi.internal.crypto.save +import net.corda.nodeapi.internal.network.NetworkParametersCopier +import net.corda.nodeapi.internal.network.NodeInfoFilesCopier +import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.testing.ALICE_NAME +import net.corda.testing.BOB_NAME +import net.corda.testing.DUMMY_BANK_A_NAME import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.* import net.corda.testing.driver.* import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index 0cb88acbc0..4c5772b308 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -12,9 +12,9 @@ import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* -import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.nodeapi.internal.config.User import net.corda.testing.SerializationEnvironmentRule +import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.getFreeLocalPorts import net.corda.testing.internal.testThreadFactory diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index e704357863..cf7cb48fa0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -7,6 +7,10 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort +import net.corda.nodeapi.internal.network.DigitalSignatureWithCert +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.* import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 2211c68b86..ffe478998d 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger -import net.corda.nodeapi.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.asContextEnv import java.nio.file.Path diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index 938bf66976..3d448d9188 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -1,7 +1,7 @@ package net.corda.testing.common.internal -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.NotaryInfo import java.time.Instant fun testNetworkParameters( diff --git a/tools/bootstrapper/build.gradle b/tools/bootstrapper/build.gradle new file mode 100644 index 0000000000..dc578b2c76 --- /dev/null +++ b/tools/bootstrapper/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'us.kirchmeier.capsule' + +description 'Network bootstrapper' + +configurations { + runtimeArtifacts +} + +// TODO Fix SLF4J warnings that occur when running the bootstrapper +task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').compileJava) { + applicationClass 'net.corda.nodeapi.internal.network.NetworkBootstrapper' + archiveName "network-bootstrapper.jar" + capsuleManifest { + applicationVersion = corda_release_version + systemProperties['visualvm.display.name'] = 'Network Bootstrapper' + minJavaVersion = '1.8.0' + jvmArgs = ['-XX:+UseG1GC'] + } + applicationSource = files( + project(':node-api').configurations.runtime, + project(':node-api').jar + ) +} + +artifacts { + runtimeArtifacts buildBootstrapperJar +} diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt index 3b856ef5ac..a3d8586d26 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt @@ -1,6 +1,6 @@ package net.corda.demobench.model -import net.corda.nodeapi.internal.NodeInfoFilesCopier +import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import rx.Scheduler import rx.schedulers.Schedulers import tornadofx.* diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index 81ddfe8881..37e019b367 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -8,12 +8,11 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.noneOrSingle import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.days import net.corda.demobench.plugin.CordappController import net.corda.demobench.pty.R3Pty -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.nodeapi.internal.NetworkParametersCopier -import net.corda.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.NetworkParametersCopier +import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.nodeapi.internal.ServiceIdentityGenerator import tornadofx.* import java.io.IOException From fc7445f71458b6a26c3d6a73d96d3f26957678d1 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 11:30:32 +0000 Subject: [PATCH 30/44] Resolve compilation issues related to Network Map changes. --- .../net/corda/confidential/IdentitySyncFlowTests.kt | 2 ++ .../net/corda/confidential/SwapIdentitiesFlowTests.kt | 2 ++ .../networkmanage/doorman/DoormanIntegrationTest.kt | 3 --- .../common/persistence/NetworkMapStorage.kt | 4 ++-- .../common/persistence/PersistentNetworkMapStorage.kt | 10 +++++----- .../common/persistence/entity/NetworkMapEntity.kt | 2 +- .../persistence/entity/NetworkParametersEntity.kt | 2 +- .../networkmanage/common/signer/NetworkMapSigner.kt | 4 ++-- .../com/r3/corda/networkmanage/common/signer/Signer.kt | 5 +---- .../com/r3/corda/networkmanage/common/utils/Utils.kt | 2 +- .../kotlin/com/r3/corda/networkmanage/doorman/Main.kt | 2 +- .../doorman/NetworkParametersConfiguration.kt | 6 ++---- .../corda/networkmanage/doorman/signer/LocalSigner.kt | 2 +- .../doorman/webservice/NodeInfoWebService.kt | 2 +- .../networkmanage/hsm/signer/HsmNetworkMapSigner.kt | 2 +- .../common/persistence/DBNetworkMapStorageTest.kt | 6 +++--- .../kotlin/net/corda/node/internal/AbstractNode.kt | 5 ++--- settings.gradle | 3 --- .../net/corda/testing/node/InMemoryMessagingNetwork.kt | 2 +- .../main/kotlin/net/corda/testing/node/MockServices.kt | 4 ++-- 20 files changed, 31 insertions(+), 39 deletions(-) diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index 653744c393..9e6a8efb43 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -19,11 +19,13 @@ import net.corda.testing.node.MockNetwork import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +@Ignore class IdentitySyncFlowTests { private lateinit var mockNet: MockNetwork 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 2c94e44717..30f0baa8b7 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -6,9 +6,11 @@ import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.Before import net.corda.testing.node.startFlow +import org.junit.Ignore import org.junit.Test import kotlin.test.* +@Ignore class SwapIdentitiesFlowTests { private lateinit var mockNet: MockNetwork diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt index 368e5337ed..3609d0ac93 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt @@ -8,11 +8,9 @@ import com.r3.corda.networkmanage.doorman.signer.LocalSigner import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData -import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert -import net.corda.core.internal.createDirectories import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort @@ -25,7 +23,6 @@ import net.corda.nodeapi.internal.crypto.* import net.corda.testing.ALICE_NAME import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.node.testNodeConfiguration import org.bouncycastle.cert.X509CertificateHolder import org.junit.Rule import org.junit.Test diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt index 4a91aa0c7c..b57e37888f 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt @@ -1,8 +1,8 @@ package com.r3.corda.networkmanage.common.persistence import net.corda.core.crypto.SecureHash -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.nodeapi.internal.SignedNetworkMap +import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.SignedNetworkMap /** * Data access object interface for NetworkMap persistence layer diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt index 477e2b7003..5ddf0a395d 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt @@ -6,9 +6,9 @@ import net.corda.core.crypto.sha256 import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.nodeapi.internal.NetworkMap -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.nodeapi.internal.SignedNetworkMap +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.persistence.CordaPersistence /** @@ -34,8 +34,8 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw database.transaction { val networkMapEntity = NetworkMapEntity( networkMap = signedNetworkMap.raw.bytes, - signature = signedNetworkMap.sig.signatureBytes, - certificate = signedNetworkMap.sig.by.encoded + signature = signedNetworkMap.signature.signatureBytes, + certificate = signedNetworkMap.signature.by.encoded ) session.save(networkMapEntity) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt index ff9865a6e1..019bed926e 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt @@ -1,7 +1,7 @@ package com.r3.corda.networkmanage.common.persistence.entity -import net.corda.nodeapi.internal.DigitalSignatureWithCert import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import net.corda.nodeapi.internal.network.DigitalSignatureWithCert import javax.persistence.* @Entity diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt index 59a2a332a1..639787ed70 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt @@ -1,7 +1,7 @@ package com.r3.corda.networkmanage.common.persistence.entity import net.corda.core.serialization.deserialize -import net.corda.nodeapi.internal.NetworkParameters +import net.corda.nodeapi.internal.network.NetworkParameters import javax.persistence.* @Entity diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt index 438ad8c17b..f3df12366a 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt @@ -3,8 +3,8 @@ package com.r3.corda.networkmanage.common.signer import com.r3.corda.networkmanage.common.persistence.CertificateStatus import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import net.corda.core.serialization.serialize -import net.corda.nodeapi.internal.NetworkMap -import net.corda.nodeapi.internal.SignedNetworkMap +import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.nodeapi.internal.network.SignedNetworkMap class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) { /** diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/Signer.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/Signer.kt index ed43651748..e00ee54335 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/Signer.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/Signer.kt @@ -1,9 +1,6 @@ package com.r3.corda.networkmanage.common.signer -import net.corda.core.crypto.DigitalSignature -import net.corda.core.serialization.CordaSerializable -import net.corda.nodeapi.internal.DigitalSignatureWithCert -import java.security.cert.CertPath +import net.corda.nodeapi.internal.network.DigitalSignatureWithCert /** * An interface for arbitrary data signing functionality. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt index 4114710625..e95b3da1b4 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt @@ -7,8 +7,8 @@ import joptsimple.ArgumentAcceptingOptionSpec import joptsimple.OptionParser import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.sha256 -import net.corda.nodeapi.internal.DigitalSignatureWithCert import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import net.corda.nodeapi.internal.network.DigitalSignatureWithCert import org.bouncycastle.cert.X509CertificateHolder import java.security.PublicKey import java.security.cert.CertPath diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index 80d347a5e7..ce34342627 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -20,8 +20,8 @@ import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor -import net.corda.nodeapi.internal.NetworkParameters import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt index 472d0418ad..90b3e6fcca 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt @@ -5,11 +5,10 @@ import com.typesafe.config.ConfigParseOptions import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.exists -import net.corda.core.utilities.days import net.corda.core.utilities.parsePublicKeyBase58 import net.corda.nodeapi.internal.config.parseAs -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.NotaryInfo import java.nio.file.Path import java.time.Instant @@ -57,7 +56,6 @@ fun parseNetworkParametersFrom(configFile: Path, epoch: Int = DEFAULT_EPOCH): Ne return NetworkParameters(networkParametersConfig.minimumPlatformVersion, networkParametersConfig.notaries.map { it.toNotaryInfo() }, - networkParametersConfig.eventHorizonDays.days, networkParametersConfig.maxMessageSize, networkParametersConfig.maxTransactionSize, Instant.now(), diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt index 8f65d4d29e..cb6e253536 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt @@ -7,9 +7,9 @@ import com.r3.corda.networkmanage.common.utils.withCert import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.internal.toX509CertHolder -import net.corda.nodeapi.internal.DigitalSignatureWithCert import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.network.DigitalSignatureWithCert import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt index 3c3cc8eb12..81b4249b33 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt @@ -12,7 +12,7 @@ import net.corda.core.crypto.SignedData import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.nodeapi.internal.SignedNetworkMap +import net.corda.nodeapi.internal.network.SignedNetworkMap import java.io.InputStream import java.security.InvalidKeyException import java.security.SignatureException diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt index 0950b1e3b8..2fec2a234c 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt @@ -13,7 +13,7 @@ import net.corda.core.internal.cert import net.corda.core.internal.toX509CertHolder import net.corda.core.utilities.loggerFor import net.corda.core.utilities.minutes -import net.corda.nodeapi.internal.DigitalSignatureWithCert +import net.corda.nodeapi.internal.network.DigitalSignatureWithCert import java.security.KeyPair import java.security.PrivateKey import java.time.Duration diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt index bde1dcdfb5..640b6d9cc3 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt @@ -13,10 +13,10 @@ import net.corda.core.internal.cert import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.internal.NetworkMap -import net.corda.nodeapi.internal.SignedNetworkMap import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties @@ -80,7 +80,7 @@ class DBNetworkMapStorageTest : TestBase() { // then val persistedSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() - assertEquals(signedNetworkMap.sig, persistedSignedNetworkMap?.sig) + assertEquals(signedNetworkMap.signature, persistedSignedNetworkMap?.signature) assertEquals(signedNetworkMap.verified(), persistedSignedNetworkMap?.verified()) } 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 91e9ae6afb..bab267c100 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -13,8 +13,8 @@ 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 +import net.corda.core.crypto.sign import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -60,8 +60,6 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager 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 @@ -69,6 +67,7 @@ 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 +import net.corda.nodeapi.internal.persistence.SchemaMigration import org.apache.activemq.artemis.utils.ReusableLatch import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry import org.slf4j.Logger diff --git a/settings.gradle b/settings.gradle index 6cbb77c684..408fd292e7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -51,9 +51,6 @@ include 'samples:notary-demo' include 'samples:bank-of-corda-demo' include 'samples:business-network-demo' include 'cordform-common' -include 'network-management' -include 'network-management:capsule' -include 'network-management:capsule-hsm' include 'verify-enclave' include 'hsm-tool' project(':hsm-tool').with { 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 cf52920202..815d1e0496 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 @@ -268,7 +268,7 @@ class InMemoryMessagingNetwork internal constructor( private val peerHandle: PeerHandle, private val executor: AffinityExecutor, private val database: CordaPersistence) : SingletonSerializeAsToken(), MessagingService { - privateinner class Handler(val topicSession: String, + private inner class Handler(val topicSession: String, val callback: MessageHandler) : MessageHandlerRegistration @Volatile diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index b4c6be5fb9..ade37332cd 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -155,9 +155,9 @@ open class MockServices private constructor( initialIdentity: TestIdentity, vararg moreKeys: KeyPair): Pair { val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) - val dataSourceProps = makeTestDataSourceProperties(initialIdentityName.organisation) + val dataSourceProps = makeTestDataSourceProperties(initialIdentity.name.organisation) val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) - val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentityName.organisation), identityService, schemaService) + val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService, schemaService) val mockService = database.transaction { object : MockServices(cordappLoader, identityService, initialIdentity, moreKeys) { override val vaultService: VaultServiceInternal = makeVaultService(database.hibernateConfig, schemaService) From 671e711ddad1ce9ce0bd5f36ea3a40cb4bd14831 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 12:00:03 +0000 Subject: [PATCH 31/44] Added missing Mock expectation. --- .../src/main/kotlin/net/corda/testing/node/MockNode.kt | 1 + 1 file changed, 1 insertion(+) 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 482ea0fa47..c4f7c25d72 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 @@ -503,5 +503,6 @@ private fun mockNodeConfiguration(): NodeConfiguration { doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec doReturn(null).whenever(it).devModeOptions doReturn(true).whenever(it).useAMQPBridges + doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration } } From 066bb767d1d0c908ceb306c7766834fe3bdb269d Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 18 Dec 2017 12:40:57 +0000 Subject: [PATCH 32/44] fixing merge errors --- .../net/corda/finance/contracts/CommercialPaperTests.kt | 4 ++-- .../kotlin/net/corda/finance/contracts/asset/CashTests.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 6b28671413..c4ef88dadc 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -270,7 +270,7 @@ class CommercialPaperTestsGeneric { @Test fun `issue move and then redeem`() { val aliceDatabaseAndServices = makeTestDatabaseAndMockServices( - listOf("net.corda.finance.contracts"), + listOf("net.corda.finance.contracts", "net.corda.finance.schemas"), makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), TestIdentity(MEGA_CORP.name, ALICE_KEY)) val databaseAlice = aliceDatabaseAndServices.first @@ -282,7 +282,7 @@ class CommercialPaperTestsGeneric { aliceVaultService = aliceServices.vaultService } val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices( - listOf("net.corda.finance.contracts"), + listOf("net.corda.finance.contracts", "net.corda.finance.schemas"), 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 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 feb9032fab..e87d83c4f6 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 @@ -103,7 +103,7 @@ 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("net.corda.finance.contracts.asset"), + listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), TestIdentity(CordaX500Name("Me", "London", "GB"))) database = databaseAndServices.first From 79f288dcf6a008e0119c5cb02818a8ec46bb7643 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 14:30:46 +0000 Subject: [PATCH 33/44] Fix compilation errors on Tests following rebase from OS. --- .../client/rpc/CordaRPCJavaClientTest.java | 2 +- .../node/services/BFTNotaryServiceTests.kt | 1 + .../services/messaging/P2PMessagingTest.kt | 5 +- .../services/vault/VaultQueryJavaTests.java | 1 + .../persistence/HibernateConfigurationTest.kt | 1 + .../persistence/RunOnceServiceTest.kt | 2 +- .../node/services/vault/VaultQueryTests.kt | 8 +--- .../contracts/CommercialPaperTests.kt | 42 +++++++++-------- .../contracts/asset/CashTests.kt | 47 +++++++++---------- .../flows/TwoPartyTradeFlowTest.kt | 9 +++- .../r3/enclaves/verify/NativeSgxApiTest.kt | 13 ++--- .../r3/enclaves/txverify/EnclaveletTest.kt | 10 ++-- 12 files changed, 75 insertions(+), 66 deletions(-) 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 7bfc292895..c10e439c0f 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 @@ -40,7 +40,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest { } @ClassRule - public static IntegrationTestSchemas databaseSchemas = new IntegrationTestSchemas(IntegrationTestKt.toDatabaseSchemaName(getALICE_NAME()), + public static IntegrationTestSchemas databaseSchemas = new IntegrationTestSchemas(IntegrationTestKt.toDatabaseSchemaName(ALICE_NAME), IntegrationTestKt.toDatabaseSchemaName(DUMMY_NOTARY_NAME)); private List perms = Arrays.asList( 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 4e6ba5d42f..e84f277e1a 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 @@ -32,6 +32,7 @@ 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.dummyCommand import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode import net.corda.testing.node.MockNodeParameters 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 4841d20750..5dfa2963b1 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,10 +101,13 @@ 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/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index 7a5b41fcc1..5150d0883b 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 @@ -25,6 +25,7 @@ import net.corda.nodeapi.internal.persistence.DatabaseTransaction; import net.corda.testing.SerializationEnvironmentRule; import net.corda.testing.TestIdentity; import net.corda.testing.internal.vault.DummyLinearContract; +import net.corda.testing.internal.vault.DummyLinearStateSchemaV1; import net.corda.testing.internal.vault.VaultFiller; import net.corda.testing.node.MockServices; import org.junit.After; 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 7844d0e52d..dc412846a6 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 @@ -38,6 +38,7 @@ import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.testing.* import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.vault.DummyDealStateSchemaV1 import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/RunOnceServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/RunOnceServiceTest.kt index d853b9608e..05568dc4dd 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/RunOnceServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/RunOnceServiceTest.kt @@ -6,9 +6,9 @@ import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever import net.corda.node.internal.configureDatabase import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties -import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Rule 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 1a977695eb..c5976b4bc7 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 @@ -18,23 +18,19 @@ import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.Commodity import net.corda.finance.contracts.DealState import net.corda.finance.contracts.asset.Cash +import net.corda.finance.sampleschemas.SampleCashSchemaV3 import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1.PersistentCashState import net.corda.finance.schemas.CommercialPaperSchemaV1 -import net.corda.finance.sampleschemas.SampleCashSchemaV3 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.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.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.DummyDealStateSchemaV1 -import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.* diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt index 76e81fc64b..b9f9d19a20 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt @@ -18,6 +18,10 @@ import net.corda.core.utilities.days import net.corda.core.utilities.seconds 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.ledger import net.corda.testing.node.makeTestIdentityService @@ -44,7 +48,7 @@ interface CommercialPaperTestTemplate { 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 KotlinCommercialPaperTest : CommercialPaperTestTemplate { @@ -89,13 +93,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 } @@ -105,7 +109,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) @@ -155,10 +159,8 @@ class CommercialPaperTestsGeneric { output(Cash.PROGRAM_ID, "Alice's profit", aliceGetsBack.STATE ownedBy ALICE) output(Cash.PROGRAM_ID, "Change", (someProfits - aliceGetsBack).STATE ownedBy MEGA_CORP) } - command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) command(ALICE_PUBKEY, thisTest.getRedeemCommand(DUMMY_NOTARY)) - tweak { outputs(700.DOLLARS `issued by` issuer) timeWindow(TEST_TX_TIME + 8.days) @@ -249,17 +251,17 @@ class CommercialPaperTestsGeneric { private lateinit var aliceVaultService: VaultService private lateinit var alicesVault: Vault - private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, dummyNotary.key) - private val issuerServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_CASH_ISSUER_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`() = withTestSerialization{ - val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices( - listOf(ALICE_KEY), - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), - initialIdentityName = MEGA_CORP.name) + @Test + fun `issue move and then redeem`() = { + val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices( + 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 @@ -270,9 +272,9 @@ class CommercialPaperTestsGeneric { } val bigCorpDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices( - listOf(BIG_CORP_KEY), - makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), - initialIdentityName = 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 @@ -298,8 +300,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_PUBKEY)) val ptx = aliceServices.signInitialTransaction(builder) val ptx2 = bigCorpServices.addSignature(ptx) val stx = notaryServices.addSignature(ptx2) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index acf6b068ce..b475c98c77 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -11,7 +11,6 @@ import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrNull import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrZero import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name @@ -28,6 +27,11 @@ 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.dsl.EnforceVerifyOrFail +import net.corda.testing.dsl.TransactionDSL +import net.corda.testing.dsl.TransactionDSLInterpreter +import net.corda.testing.internal.LogHelper +import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.ledger @@ -77,27 +81,27 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, 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 @@ -133,22 +137,15 @@ class CashTests { @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) - megaCorpServices = MockServices( - listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), - rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) - miniCorpServices = MockServices( - listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), - rigorousMock().also { - doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) - }, MINI_CORP.name, MINI_CORP_KEY) - val notaryServices = MockServices( - listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), - rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) + miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), rigorousMock().also { + doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) + }, 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( - cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), - initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"), - keys = listOf(generateKeyPair()), - identityService = makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY))) + 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 @@ -184,7 +181,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) @@ -882,7 +879,7 @@ class CashTests { transaction { attachment(Cash.PROGRAM_ID) input("MEGA_CORP cash") - // We send it to another pubkey so that the transaction is not identical to the previous one + // We send it to another publicKey so that the transaction is not identical to the previous one output(Cash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output().copy(owner = ALICE)) command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) this.verifies() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index 1ddfe67308..dbaaaab191 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -47,6 +47,12 @@ import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.statemachine.Checkpoint import net.corda.nodeapi.internal.persistence.CordaPersistence 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.LogHelper +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 @@ -127,8 +133,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { bobNode.internals.disableDBCloseOnStop() bobNode.database.transaction { - bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, outputNotary = notary, - issuedBy = cashIssuer) + VaultFiller(bobNode.services, dummyNotary, notary, ::Random).fillWithSomeTestCash(2000.DOLLARS, bankNode.services, 3, 10, cashIssuer) } val alicesFakePaper = aliceNode.database.transaction { diff --git a/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt b/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt index 198fe59fbe..81699c31a9 100644 --- a/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt +++ b/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt @@ -17,9 +17,9 @@ import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.DUMMY_NOTARY_NAME import net.corda.testing.TestIdentity import net.corda.testing.getTestPartyAndCertificate +import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.ledger -import net.corda.testing.rigorousMock import org.junit.Ignore import org.junit.Test import java.math.BigInteger @@ -34,13 +34,14 @@ class NativeSgxApiTest { val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val MEGA_CORP get() = megaCorp.party - val MEGA_CORP_PUBKEY get() = megaCorp.pubkey - val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).pubkey + val MEGA_CORP_PUBKEY get() = megaCorp.publicKey + val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).publicKey } - private val ledgerServices = MockServices(rigorousMock().also { - doReturn(NativeSgxApiTest.MEGA_CORP).whenever(it).partyFromKey(NativeSgxApiTest.MEGA_CORP_PUBKEY) - }, NativeSgxApiTest.MEGA_CORP.name) + private val identityService = rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + } + private val ledgerServices = MockServices(emptyList(), identityService, MEGA_CORP.name) @Ignore("The SGX code is not part of the standard build yet") @Test diff --git a/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt b/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt index 66741aec84..066d5b94ee 100644 --- a/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt +++ b/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.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.Ignore @@ -31,16 +32,17 @@ class EnclaveletTest { val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val MEGA_CORP get() = megaCorp.party - val MEGA_CORP_PUBKEY get() = megaCorp.pubkey - val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).pubkey + val MEGA_CORP_PUBKEY get() = megaCorp.publicKey + val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).publicKey } @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - private val ledgerServices = MockServices(rigorousMock().also { + private val identityService = rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - }, MEGA_CORP.name) + } + private val ledgerServices = MockServices(emptyList(), identityService, MEGA_CORP.name) @Ignore("Pending Gradle bug: https://github.com/gradle/gradle/issues/2657") @Test From f3f06976d0e1c3d7afc145b9a30e71a09f1517f6 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 15:23:16 +0000 Subject: [PATCH 34/44] Fix failing perftestcordapp tests. --- .../contracts/CommercialPaperTests.kt | 12 ++++++------ .../perftestcordapp/contracts/asset/CashTests.kt | 8 ++++---- .../perftestcordapp/flows/TwoPartyTradeFlowTest.kt | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt index b9f9d19a20..777769d03f 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt @@ -256,12 +256,12 @@ class CommercialPaperTestsGeneric { private lateinit var moveTX: SignedTransaction - @Test - fun `issue move and then redeem`() = { - val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices( - 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)) + @Test + fun `issue move and then redeem`() { + val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices( + 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 diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index b475c98c77..a9dec35c86 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -137,13 +137,13 @@ class CashTests { @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) - megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) - miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), rigorousMock().also { + megaCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) + miniCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock().also { doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) }, MINI_CORP.name, MINI_CORP_KEY) - val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + val notaryServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val databaseAndServices = makeTestDatabaseAndMockServices( - listOf("net.corda.finance.contracts.asset"), + listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), TestIdentity(CordaX500Name("Me", "London", "GB"))) database = databaseAndServices.first diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index dbaaaab191..b41e869971 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -52,7 +52,6 @@ import net.corda.testing.dsl.TestLedgerDSLInterpreter import net.corda.testing.dsl.TestTransactionDSLInterpreter import net.corda.testing.internal.LogHelper 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 @@ -133,7 +132,8 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { bobNode.internals.disableDBCloseOnStop() bobNode.database.transaction { - VaultFiller(bobNode.services, dummyNotary, notary, ::Random).fillWithSomeTestCash(2000.DOLLARS, bankNode.services, 3, 10, cashIssuer) + bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, outputNotary = notary, + issuedBy = cashIssuer) } val alicesFakePaper = aliceNode.database.transaction { From 107fcf82e39539fff7450b860d64b2c37726f378 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 18 Dec 2017 15:39:00 +0000 Subject: [PATCH 35/44] Fixing stuff --- build.gradle | 27 -- .../client/rpc/CordaRPCJavaClientTest.java | 3 +- .../confidential/IdentitySyncFlowTests.kt | 2 - .../confidential/SwapIdentitiesFlowTests.kt | 2 - .../corda/core/node/services/NotaryService.kt | 12 +- docs/source/changelog.rst | 1 - .../finance/contracts/CommercialPaperTests.kt | 4 +- .../finance/contracts/asset/CashTests.kt | 12 +- .../doorman/DoormanIntegrationTest.kt | 47 ++-- .../hsm/SigningServiceIntegrationTest.kt | 58 +++-- .../common/signer/NetworkMapSigner.kt | 2 +- .../doorman/NetworkParametersConfiguration.kt | 1 + .../NetworkParametersConfigurationTest.kt | 1 - .../com/r3/corda/networkmanage/TestBase.kt | 5 +- .../persistence/DBNetworkMapStorageTest.kt | 2 +- .../common/signer/NetworkMapSignerTest.kt | 8 +- .../doorman/NodeInfoWebServiceTest.kt | 6 +- .../nodeapi/internal/network/NetworkMap.kt | 6 +- .../serialization/SetsSerializationTest.kt | 2 +- node/build.gradle | 2 + .../node/services/AttachmentLoadingTests.kt | 2 +- .../node/services/BFTNotaryServiceTests.kt | 13 +- .../node/services/MySQLNotaryServiceTests.kt | 154 +++++++++++ .../node/services/network/NetworkMapTest.kt | 17 +- .../registration/NodeRegistrationTest.kt | 10 +- .../net/corda/node/internal/AbstractNode.kt | 23 +- .../node/services/config/NodeConfiguration.kt | 7 +- .../transactions/MySQLNotaryService.kt | 52 ++++ .../transactions/MySQLUniquenessProvider.kt | 162 ++++++++++++ node/src/main/resources/reference.conf | 3 +- .../services/vault/VaultQueryJavaTests.java | 7 +- .../persistence/DBCheckpointStorageTests.kt | 7 +- .../persistence/HibernateConfigurationTest.kt | 10 +- .../node/services/vault/VaultWithCashTest.kt | 7 +- .../contracts/CommercialPaperTests.kt | 120 +-------- .../contracts/asset/CashTests.kt | 20 +- settings.gradle | 3 + .../testing/node/InMemoryMessagingNetwork.kt | 3 +- .../kotlin/net/corda/testing/node/MockNode.kt | 3 +- .../net/corda/testing/node/NodeTestUtils.kt | 4 +- tools/jmeter/build.gradle | 3 + .../src/main/resources/Testplans/SQL.jmx | 244 +++++++++++++++++ .../resources/Testplans/SQL_parmeterized.jmx | 246 ++++++++++++++++++ .../r3/enclaves/verify/NativeSgxApiTest.kt | 11 +- .../r3/enclaves/txverify/EnclaveletTest.kt | 9 +- 45 files changed, 1060 insertions(+), 283 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/transactions/MySQLUniquenessProvider.kt create mode 100644 tools/jmeter/src/main/resources/Testplans/SQL.jmx create mode 100644 tools/jmeter/src/main/resources/Testplans/SQL_parmeterized.jmx diff --git a/build.gradle b/build.gradle index 2da8daa2e8..98a82c7fc2 100644 --- a/build.gradle +++ b/build.gradle @@ -162,33 +162,6 @@ allprojects { tasks.withType(Test) { // Prevent the project from creating temporary files outside of the build directory. systemProperties['java.io.tmpdir'] = buildDir - - // Ensures that "net.corda.testing.amqp.enable" is passed correctly from Gradle command line - // down to JVM executing unit test. It looks like we are running unit tests in the forked mode - // and all the "-D" parameters passed to Gradle not making it to unit test level - // TODO: Remove once we fully switched to AMQP - final AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable" - systemProperty(AMQP_ENABLE_PROP_NAME, System.getProperty(AMQP_ENABLE_PROP_NAME)) - - // relational database provider to be used by node - final DATABASE_PROVIDER = "databaseProvider" - final DATASOURCE_URL = "dataSourceProperties.dataSource.url" - final DATASOURCE_CLASSNAME = "dataSourceProperties.dataSourceClassName" - final DATASOURCE_USER = "dataSourceProperties.dataSource.user" - final DATASOURCE_PASSWORD = "dataSourceProperties.dataSource.password" - - // integration testing database configuration (to be used in conjunction with a DATABASE_PROVIDER) - final TEST_DB_ADMIN_USER = "test.db.admin.user" - final TEST_DB_ADMIN_PASSWORD = "test.db.admin.password" - final TEST_DB_SCRIPT_DIR = "test.db.script.dir" - - [DATABASE_PROVIDER,DATASOURCE_URL, DATASOURCE_CLASSNAME, DATASOURCE_USER, DATASOURCE_PASSWORD, - TEST_DB_ADMIN_USER, TEST_DB_ADMIN_PASSWORD, TEST_DB_SCRIPT_DIR].forEach { - def property = System.getProperty(it) - if (property != null) { - systemProperty(it, property) - } - } } group 'com.r3.corda.enterprise' 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 c10e439c0f..849e9f3d37 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 @@ -63,8 +63,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest { } @Before - public void setUp() throws Exception { - super.setUp(); + public void setUp() throws ExecutionException, InterruptedException { node = startNode(ALICE_NAME, 1, singletonList(rpcUser)); client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress())); } diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index 9e6a8efb43..653744c393 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -19,13 +19,11 @@ import net.corda.testing.node.MockNetwork import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -@Ignore class IdentitySyncFlowTests { private lateinit var mockNet: MockNetwork 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 30f0baa8b7..2c94e44717 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -6,11 +6,9 @@ import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.Before import net.corda.testing.node.startFlow -import org.junit.Ignore import org.junit.Test import kotlin.test.* -@Ignore class SwapIdentitiesFlowTests { private lateinit var mockNet: MockNetwork diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index 46227d0027..0c928a7b59 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -16,13 +16,21 @@ import java.security.PublicKey abstract class NotaryService : SingletonSerializeAsToken() { companion object { const val ID_PREFIX = "corda.notary." - fun constructId(validating: Boolean, raft: Boolean = false, bft: Boolean = false, custom: Boolean = false): String { - require(Booleans.countTrue(raft, bft, custom) <= 1) { "At most one of raft, bft or custom may be true" } + @JvmOverloads + fun constructId( + validating: Boolean, + raft: Boolean = false, + bft: Boolean = false, + custom: Boolean = false, + mysql: Boolean = false + ): String { + require(Booleans.countTrue(raft, bft, custom, mysql) <= 1) { "At most one of raft, bft, mysql or custom may be true" } return StringBuffer(ID_PREFIX).apply { append(if (validating) "validating" else "simple") if (raft) append(".raft") if (bft) append(".bft") if (custom) append(".custom") + if (mysql) append(".mysql") }.toString() } } diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 1bdeb80ede..60959b5695 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,7 +6,6 @@ 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 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 c4ef88dadc..bb7fd9dd0c 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -15,7 +15,9 @@ import net.corda.core.utilities.days import net.corda.core.utilities.seconds import net.corda.finance.DOLLARS import net.corda.finance.`issued by` -import net.corda.finance.contracts.asset.* +import net.corda.finance.contracts.asset.CASH +import net.corda.finance.contracts.asset.Cash +import net.corda.finance.contracts.asset.STATE import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.dsl.EnforceVerifyOrFail 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 e87d83c4f6..8476c1f1ba 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 @@ -1,9 +1,15 @@ package net.corda.finance.contracts.asset -import com.nhaarman.mockito_kotlin.* +import com.nhaarman.mockito_kotlin.argThat +import com.nhaarman.mockito_kotlin.doNothing +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash -import net.corda.core.identity.* +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.services.VaultService import net.corda.core.node.services.queryBy @@ -20,10 +26,10 @@ 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.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.LogHelper import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.node.MockServices diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt index 3609d0ac93..fe7633cfa6 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt @@ -1,5 +1,6 @@ package com.r3.corda.networkmanage.doorman +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.utils.buildCertPath @@ -8,22 +9,29 @@ import com.r3.corda.networkmanage.doorman.signer.LocalSigner import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData +import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.NetworkMapClient import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper +import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.* import net.corda.testing.ALICE_NAME import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.internal.rigorousMock import org.bouncycastle.cert.X509CertificateHolder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -48,16 +56,11 @@ class DoormanIntegrationTest { //Start doorman server val doorman = startDoorman(intermediateCertAndKey, rootCertAndKey.certificate) - + val doormanHostAndPort = doorman.hostAndPort // Start Corda network registration. - val config = testNodeConfiguration( - baseDirectory = tempFolder.root.toPath(), - myLegalName = ALICE_NAME).also { - val doormanHostAndPort = doorman.hostAndPort - whenever(it.compatibilityZoneURL).thenReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")) - whenever(it.emailAddress).thenReturn("iTest@R3.com") + val config = createConfig().also { + doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL } - config.trustStoreFile.parent.createDirectories() loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCertAndKey.certificate.cert) @@ -94,6 +97,7 @@ class DoormanIntegrationTest { doorman.close() } + @Ignore @Test fun `nodeInfo is published to the network map`() { // Given @@ -105,13 +109,9 @@ class DoormanIntegrationTest { val doormanHostAndPort = doorman.hostAndPort // Start Corda network registration. - val config = testNodeConfiguration( - baseDirectory = tempFolder.root.toPath(), - myLegalName = ALICE_NAME).also { - whenever(it.compatibilityZoneURL).thenReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")) - whenever(it.emailAddress).thenReturn("iTest@R3.com") + val config = createConfig().also { + doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL } - config.trustStoreFile.parent.createDirectories() loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCertAndKey.certificate.cert) @@ -128,7 +128,8 @@ class DoormanIntegrationTest { val nodeInfoBytes = nodeInfo.serialize() // When - networkMapClient.publish(SignedData(nodeInfoBytes, keyPair.sign(nodeInfoBytes))) + val signedNodeInfo = SignedNodeInfo(nodeInfoBytes, listOf(keyPair.sign(nodeInfoBytes))) + networkMapClient.publish(signedNodeInfo) // Then val networkMapNodeInfo = networkMapClient.getNodeInfo(nodeInfoBytes.hash) @@ -137,8 +138,24 @@ class DoormanIntegrationTest { doorman.close() } + + fun createConfig(): NodeConfiguration { + return rigorousMock().also { + doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory + doReturn(ALICE_NAME).whenever(it).myLegalName + doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory + doReturn(it.certificatesDirectory / "truststore.jks").whenever(it).trustStoreFile + doReturn(it.certificatesDirectory / "nodekeystore.jks").whenever(it).nodeKeystore + doReturn(it.certificatesDirectory / "sslkeystore.jks").whenever(it).sslKeystore + doReturn("trustpass").whenever(it).trustStorePassword + doReturn("cordacadevpass").whenever(it).keyStorePassword +// doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL + doReturn("iTest@R3.com").whenever(it).emailAddress + } + } } + fun createDoormanIntermediateCertificateAndKeyPair(rootCertificateAndKeyPair: CertificateAndKeyPair): CertificateAndKeyPair { val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCertificateAndKeyPair.certificate, rootCertificateAndKeyPair.keyPair, diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt index 7ce747fc5f..7616b07d60 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt @@ -1,9 +1,6 @@ package com.r3.corda.networkmanage.hsm -import com.nhaarman.mockito_kotlin.any -import com.nhaarman.mockito_kotlin.mock -import com.nhaarman.mockito_kotlin.verify -import com.nhaarman.mockito_kotlin.whenever +import com.nhaarman.mockito_kotlin.* import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.toX509Certificate @@ -17,9 +14,11 @@ import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert import net.corda.core.internal.createDirectories +import net.corda.core.internal.div import net.corda.core.internal.uncheckedCast import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds +import net.corda.node.services.config.NodeConfiguration import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.nodeapi.internal.crypto.* @@ -28,7 +27,7 @@ import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME import net.corda.testing.CHARLIE_NAME import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.node.testNodeConfiguration +import net.corda.testing.internal.rigorousMock import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.h2.tools.Server @@ -101,14 +100,12 @@ class SigningServiceIntegrationTest { NetworkManagementServer().use { server -> server.start(NetworkHostAndPort(HOST, 0), database, networkMapServiceParameter = null, doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null), updateNetworkParameters = null) + val doormanHostAndPort = server.hostAndPort // Start Corda network registration. - val config = testNodeConfiguration( - baseDirectory = tempFolder.root.toPath(), - myLegalName = ALICE_NAME).also { - val doormanHostAndPort = server.hostAndPort - whenever(it.compatibilityZoneURL).thenReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")) + val config = createConfig().also { + doReturn(ALICE_NAME).whenever(it).myLegalName + doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL } - val signingServiceStorage = DBSignedCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties())) val hsmSigner = givenSignerSigningAllRequests(signingServiceStorage) @@ -149,6 +146,7 @@ class SigningServiceIntegrationTest { * The split is done due to the limited console support while executing tests and inability to capture user's input there. * */ + @Ignore @Test fun `DEMO - Create CSR and poll`() { //Start doorman server @@ -162,23 +160,41 @@ class SigningServiceIntegrationTest { } // Start Corda network registration. - (1..3).map { + (1..3).map { num -> thread(start = true) { - val config = testNodeConfiguration( - baseDirectory = tempFolder.root.toPath(), - myLegalName = when (it) { - 1 -> ALICE_NAME - 2 -> BOB_NAME - 3 -> CHARLIE_NAME - else -> throw IllegalArgumentException("Unrecognised option") - }).also { - whenever(it.compatibilityZoneURL).thenReturn(URL("http://$HOST:${server.hostAndPort.port}")) + // Start Corda network registration. + val config = createConfig().also { + doReturn(when (num) { + 1 -> ALICE_NAME + 2 -> BOB_NAME + 3 -> CHARLIE_NAME + else -> throw IllegalArgumentException("Unrecognised option") + }).whenever(it).myLegalName + doReturn(URL("http://$HOST:${server.hostAndPort.port}")).whenever(it).compatibilityZoneURL + } + config.trustStoreFile.parent.createDirectories() + loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { + it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) + it.save(config.trustStoreFile, config.trustStorePassword) } NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() } }.map { it.join() } } } + + fun createConfig(): NodeConfiguration { + return rigorousMock().also { + doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory + doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory + doReturn(it.certificatesDirectory / "truststore.jks").whenever(it).trustStoreFile + doReturn(it.certificatesDirectory / "nodekeystore.jks").whenever(it).nodeKeystore + doReturn(it.certificatesDirectory / "sslkeystore.jks").whenever(it).sslKeystore + doReturn("trustpass").whenever(it).trustStorePassword + doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn("iTest@R3.com").whenever(it).emailAddress + } + } } private fun makeTestDataSourceProperties(): Properties { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt index f3df12366a..b3913936b3 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt @@ -15,7 +15,7 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID) val networkParameters = networkMapStorage.getLatestNetworkParameters() val networkMap = NetworkMap(nodeInfoHashes, networkParameters.serialize().hash) - if (networkMap != currentSignedNetworkMap?.verified()) { + if (networkMap != currentSignedNetworkMap?.verified(null)) { val digitalSignature = signer.sign(networkMap.serialize().bytes) val signedHashedNetworkMap = SignedNetworkMap(networkMap.serialize(), digitalSignature) networkMapStorage.saveNetworkMap(signedHashedNetworkMap) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt index 90b3e6fcca..7186c4572e 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt @@ -5,6 +5,7 @@ import com.typesafe.config.ConfigParseOptions import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.exists +import net.corda.core.utilities.days import net.corda.core.utilities.parsePublicKeyBase58 import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.network.NetworkParameters diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt index cf9fd34681..24b6f2451c 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt @@ -17,7 +17,6 @@ class NetworkParametersConfigurationTest { fun `reads an existing file`() { val networkParameters = parseNetworkParametersFrom(validOverrideNetworkConfigPath) assertThat(networkParameters.minimumPlatformVersion).isEqualTo(1) - assertThat(networkParameters.eventHorizon).isEqualTo(100.days) val notaries = networkParameters.notaries assertThat(notaries).hasSize(2) assertThat(notaries[0].validating).isTrue() diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt index 75984ac3be..4b9c35f990 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt @@ -7,8 +7,8 @@ import com.r3.corda.networkmanage.common.persistence.CertificateStatus import com.r3.corda.networkmanage.common.persistence.RequestStatus import net.corda.core.crypto.SecureHash import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.NetworkParameters -import net.corda.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.testing.SerializationEnvironmentRule import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.junit.Rule @@ -62,7 +62,6 @@ abstract class TestBase { return NetworkParameters( minimumPlatformVersion = minimumPlatformVersion, notaries = notaries, - eventHorizon = eventHorizon, maxMessageSize = maxMessageSize, maxTransactionSize = maxTransactionSize, modifiedTime = modifiedTime, diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt index 640b6d9cc3..5e65e3d28a 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt @@ -81,7 +81,7 @@ class DBNetworkMapStorageTest : TestBase() { val persistedSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() assertEquals(signedNetworkMap.signature, persistedSignedNetworkMap?.signature) - assertEquals(signedNetworkMap.verified(), persistedSignedNetworkMap?.verified()) + assertEquals(signedNetworkMap.verified(rootCACert.cert), persistedSignedNetworkMap?.verified(rootCACert.cert)) } @Test diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt index 29e05f0ae5..9a65b68449 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt @@ -11,10 +11,10 @@ import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert import net.corda.core.serialization.serialize -import net.corda.nodeapi.internal.NetworkMap -import net.corda.nodeapi.internal.SignedNetworkMap import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.nodeapi.internal.network.SignedNetworkMap import org.junit.Before import org.junit.Test import kotlin.test.assertEquals @@ -58,7 +58,7 @@ class NetworkMapSignerTest : TestBase() { verify(networkMapStorage).getLatestNetworkParameters() argumentCaptor().apply { verify(networkMapStorage).saveNetworkMap(capture()) - val networkMap = firstValue.verified() + val networkMap = firstValue.verified(rootCACert.cert) assertEquals(networkMapParameters.serialize().hash, networkMap.networkParameterHash) assertEquals(signedNodeInfoHashes.size, networkMap.nodeInfoHashes.size) assertTrue(networkMap.nodeInfoHashes.containsAll(signedNodeInfoHashes)) @@ -104,7 +104,7 @@ class NetworkMapSignerTest : TestBase() { verify(networkMapStorage).getLatestNetworkParameters() argumentCaptor().apply { verify(networkMapStorage).saveNetworkMap(capture()) - val networkMap = firstValue.verified() + val networkMap = firstValue.verified(rootCACert.cert) assertEquals(networkMapParameters.serialize().hash, networkMap.networkParameterHash) } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt index a4c2166560..856c15fdae 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt @@ -19,10 +19,10 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.NetworkMap -import net.corda.nodeapi.internal.SignedNetworkMap import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.testing.SerializationEnvironmentRule import org.bouncycastle.asn1.x500.X500Name import org.junit.Rule @@ -82,7 +82,7 @@ class NodeInfoWebServiceTest { val conn = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}").openConnection() as HttpURLConnection val signedNetworkMap = conn.inputStream.readBytes().deserialize() verify(networkMapStorage, times(1)).getCurrentNetworkMap() - assertEquals(signedNetworkMap.verified(), networkMap) + assertEquals(signedNetworkMap.verified(rootCACert.cert), networkMap) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt index f784a4297b..046b5ee7be 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt @@ -68,10 +68,12 @@ class SignedNetworkMap(val raw: SerializedBytes, val signature: Digi * @throws SignatureException if the signature is invalid. */ @Throws(SignatureException::class, CertPathValidatorException::class) - fun verified(trustedRoot: X509Certificate): NetworkMap { + 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) + if (trustedRoot != null) { + X509Utilities.validateCertificateChain(trustedRoot, signature.by, trustedRoot) + } return raw.deserialize() } } 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 4d66588057..3e1ede0733 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.internal.kryoSpecific import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Rule diff --git a/node/build.gradle b/node/build.gradle index f6cbaf4eb6..361c2b3333 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -127,6 +127,8 @@ dependencies { compile "org.postgresql:postgresql:$postgresql_version" //For Azure SQL and SQL Server support in persistence compile 'com.microsoft.sqlserver:mssql-jdbc:6.2.1.jre8' + // For the MySQLUniquenessProvider + compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6' // SQL connection pooling library compile "com.zaxxer:HikariCP:2.5.1" 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 3b5159e6cb..cd6c10c3eb 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,9 +24,9 @@ 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.rigorousMock 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 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 e84f277e1a..3c65d0e2a2 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 @@ -25,11 +25,9 @@ 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.ServiceIdentityGenerator +import net.corda.nodeapi.internal.network.NetworkParametersCopier 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.dummyCommand @@ -39,19 +37,12 @@ import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before -import org.junit.ClassRule import org.junit.Test import java.nio.file.Paths import kotlin.test.assertEquals import kotlin.test.assertTrue -class BFTNotaryServiceTests : IntegrationTest() { - companion object { - @ClassRule @JvmField - val databaseSchemas = IntegrationTestSchemas("node_0", "node_1", "node_2", "node_3", "node_4", "node_5", - "node_6", "node_7", "node_8", "node_9") - } - +class BFTNotaryServiceTests { private lateinit var mockNet: MockNetwork private lateinit var notary: Party private lateinit var node: StartedNode diff --git a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt new file mode 100644 index 0000000000..f1f467d24e --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt @@ -0,0 +1,154 @@ +package net.corda.node.services + +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef +import net.corda.core.crypto.TransactionSignature +import net.corda.core.flows.NotaryError +import net.corda.core.flows.NotaryException +import net.corda.core.flows.NotaryFlow +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.StartedNode +import net.corda.node.services.config.NotaryConfig +import net.corda.nodeapi.internal.ServiceIdentityGenerator +import net.corda.nodeapi.internal.network.NetworkParametersCopier +import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.testing.* +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.contracts.DummyContract +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.node.startFlow +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Test +import java.math.BigInteger +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class MySQLNotaryServiceTests : IntegrationTest() { + companion object { + val notaryName = CordaX500Name("MySQL Notary Service", "Zurich", "CH") + @ClassRule + @JvmField + val databaseSchemas = IntegrationTestSchemas("node_0", DUMMY_NOTARY_NAME.toDatabaseSchemaName()) + } + + private lateinit var mockNet: MockNetwork + private lateinit var node: StartedNode + private lateinit var notaryParty: Party + private lateinit var notaryNode: StartedNode + + @Before + fun before() { + mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) + notaryParty = ServiceIdentityGenerator.generateToDisk( + listOf(mockNet.baseDirectory(mockNet.nextNodeId)), + notaryName, + "identity" + ) + val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryParty, false)))) + val notaryNodeUnstarted = createNotaryNode() + val nodeUnstarted = mockNet.createUnstartedNode() + + val startedNodes = listOf(notaryNodeUnstarted, nodeUnstarted).map { n -> + networkParameters.install(mockNet.baseDirectory(n.id)) + n.start() + } + notaryNode = startedNodes.first() + node = startedNodes.last() + } + + @After + fun stopNodes() { + mockNet.stopNodes() + } + + @Test + fun `detect double spend`() { + val inputState = issueState(node, notaryParty) + + val firstTxBuilder = TransactionBuilder(notaryParty) + .addInputState(inputState) + .addCommand(dummyCommand(node.services.myInfo.chooseIdentity().owningKey)) + val firstSpendTx = node.services.signInitialTransaction(firstTxBuilder) + + val firstSpend = node.services.startFlow(NotaryFlow.Client(firstSpendTx)) + mockNet.runNetwork() + + firstSpend.resultFuture.getOrThrow() + + val secondSpendBuilder = TransactionBuilder(notaryParty).withItems(inputState).run { + val dummyState = DummyContract.SingleOwnerState(0, node.info.chooseIdentity()) + addOutputState(dummyState, DummyContract.PROGRAM_ID) + addCommand(dummyCommand(node.services.myInfo.chooseIdentity().owningKey)) + this + } + val secondSpendTx = node.services.signInitialTransaction(secondSpendBuilder) + val secondSpend = node.services.startFlow(NotaryFlow.Client(secondSpendTx)) + + mockNet.runNetwork() + + val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() } + val error = ex.error as NotaryError.Conflict + assertEquals(error.txId, secondSpendTx.id) + } + + @Test + fun `notarisations are idempotent`() { + val inputState = issueState(node, notaryParty) + + val txBuilder = TransactionBuilder(notaryParty) + .addInputState(inputState) + .addCommand(dummyCommand(node.services.myInfo.chooseIdentity().owningKey)) + val spendTx = node.services.signInitialTransaction(txBuilder) + + val notarise = node.services.startFlow(NotaryFlow.Client(spendTx)) + mockNet.runNetwork() + val signature = notarise.resultFuture.get().single() + + val notariseRetry = node.services.startFlow(NotaryFlow.Client(spendTx)) + mockNet.runNetwork() + val signatureRetry = notariseRetry.resultFuture.get().single() + + fun checkSignature(signature: TransactionSignature) { + signature.verify(spendTx.id) + assertEquals(notaryParty.owningKey, signature.by) + } + + checkSignature(signature) + checkSignature(signatureRetry) + } + + private fun createNotaryNode(): MockNetwork.MockNode { + val dataStoreProperties = makeTestDataSourceProperties().apply { + setProperty("autoCommit", "false") + } + return mockNet.createUnstartedNode( + MockNodeParameters( + legalName = notaryName, + entropyRoot = BigInteger.valueOf(60L), + configOverrides = { + val notaryConfig = NotaryConfig(validating = false, mysql = dataStoreProperties) + doReturn(notaryConfig).whenever(it).notary + } + ) + ) + } + + private fun issueState(node: StartedNode<*>, notary: Party): StateAndRef<*> { + return node.database.transaction { + val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.chooseIdentity().ref(0)) + val stx = node.services.signInitialTransaction(builder) + node.services.recordTransactions(stx) + StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0)) + } + } +} 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 c890e1f6cd..a46ae6f378 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 @@ -11,24 +11,21 @@ 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.SerializationEnvironmentRule import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation +import net.corda.testing.node.internal.CompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat -import org.junit.* +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test import java.net.URL import kotlin.test.assertEquals -class NetworkMapTest : IntegrationTest() { - companion object { - @ClassRule - @JvmField - val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName(), - DUMMY_NOTARY_NAME.toDatabaseSchemaName()) - } +class NetworkMapTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) 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 3465caebdc..bfc30a96d7 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 @@ -15,10 +15,9 @@ 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.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.CompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat @@ -27,7 +26,6 @@ import org.bouncycastle.pkcs.PKCS10CertificationRequest 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 @@ -42,11 +40,7 @@ import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response -class NodeRegistrationTest : IntegrationTest() { - companion object { - @ClassRule @JvmField - val databaseSchemas = IntegrationTestSchemas("Alice") - } +class NodeRegistrationTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) 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 bab267c100..b25bb1df9d 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -697,14 +697,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService { val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service") return notaryConfig.run { - if (raft != null) { - val uniquenessProvider = RaftUniquenessProvider(configuration, database, services.monitoringService.metrics, raft) - (if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider) - } else if (bftSMaRt != null) { - if (validating) throw IllegalArgumentException("Validating BFTSMaRt notary not supported") - BFTNonValidatingNotaryService(services, notaryKey, bftSMaRt, makeBFTCluster(notaryKey, bftSMaRt)) - } else { - (if (validating) ::ValidatingNotaryService else ::SimpleNotaryService)(services, notaryKey) + when { + raft != null -> { + val uniquenessProvider = RaftUniquenessProvider(configuration, database, services.monitoringService.metrics, raft) + (if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider) + } + bftSMaRt != null -> { + if (validating) throw IllegalArgumentException("Validating BFTSMaRt notary not supported") + BFTNonValidatingNotaryService(services, notaryKey, bftSMaRt, makeBFTCluster(notaryKey, bftSMaRt)) + } + mysql != null -> { + (if (validating) ::MySQLValidatingNotaryService else ::MySQLNonValidatingNotaryService)(services, notaryKey, mysql, configuration.devMode) + } + else -> (if (validating) ::ValidatingNotaryService else ::SimpleNotaryService)(services, notaryKey) } } } @@ -754,7 +759,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, Pair("identity", myLegalName) } else { val notaryId = notaryConfig.run { - NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom) + NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom, mysql != null) } // The node is part of a distributed notary whose identity must already be generated beforehand. Pair(notaryId, null) 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 705f4e4fc0..315598c00b 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 @@ -56,11 +56,12 @@ fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { data class NotaryConfig(val validating: Boolean, val raft: RaftConfig? = null, val bftSMaRt: BFTSMaRtConfiguration? = null, - val custom: Boolean = false + val custom: Boolean = false, + val mysql: Properties? = null ) { init { - require(raft == null || bftSMaRt == null || !custom) { - "raft, bftSMaRt, and custom configs cannot be specified together" + require(raft == null || bftSMaRt == null || !custom || mysql == null) { + "raft, bftSMaRt, custom, and mysql configs cannot be specified together" } } val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt new file mode 100644 index 0000000000..0bf810b2ab --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt @@ -0,0 +1,52 @@ +package net.corda.node.services.transactions + +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.node.services.TimeWindowChecker +import net.corda.core.node.services.TrustedAuthorityNotaryService +import net.corda.node.services.api.ServiceHubInternal +import java.security.PublicKey +import java.util.* + +/** Notary service backed by a replicated MySQL database. */ +abstract class MySQLNotaryService( + final override val services: ServiceHubInternal, + override val notaryIdentityKey: PublicKey, + dataSourceProperties: Properties, + /** Database table will be automatically created in dev mode */ + val devMode: Boolean) : TrustedAuthorityNotaryService() { + + override val timeWindowChecker = TimeWindowChecker(services.clock) + override val uniquenessProvider = MySQLUniquenessProvider( + services.monitoringService.metrics, + dataSourceProperties + ) + + override fun start() { + if (devMode) uniquenessProvider.createTable() + } + + override fun stop() { + uniquenessProvider.stop() + } +} + +class MySQLNonValidatingNotaryService(services: ServiceHubInternal, + notaryIdentityKey: PublicKey, + dataSourceProperties: Properties, + devMode: Boolean = false) : MySQLNotaryService(services, notaryIdentityKey, dataSourceProperties, devMode) { + companion object { + val id = constructId(validating = false, mysql = true) + } + override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = NonValidatingNotaryFlow(otherPartySession, this) +} + +class MySQLValidatingNotaryService(services: ServiceHubInternal, + notaryIdentityKey: PublicKey, + dataSourceProperties: Properties, + devMode: Boolean = false) : MySQLNotaryService(services, notaryIdentityKey, dataSourceProperties, devMode) { + companion object { + val id = constructId(validating = true, mysql = true) + } + override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = ValidatingNotaryFlow(otherPartySession, this) +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/MySQLUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/MySQLUniquenessProvider.kt new file mode 100644 index 0000000000..33fe84d79c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/transactions/MySQLUniquenessProvider.kt @@ -0,0 +1,162 @@ +package net.corda.node.services.transactions + +import com.codahale.metrics.MetricRegistry +import com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import net.corda.core.contracts.StateRef +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.node.services.UniquenessException +import net.corda.core.node.services.UniquenessProvider +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.loggerFor +import java.security.PublicKey +import java.sql.BatchUpdateException +import java.sql.Connection +import java.util.* + +/** + * Uniqueness provider backed by a MySQL database. It is intended to be used with a multi-master synchronously replicated + * variant of MySQL, such as Percona XtraDB Cluster, or MariaDB Galera Cluster. + * + * Note that no ORM is used since we want to retain full control over table schema and be able to experiment with optimisations. + */ +class MySQLUniquenessProvider( + metrics: MetricRegistry, + dataSourceProperties: Properties +) : UniquenessProvider, SingletonSerializeAsToken() { + companion object { + private val log = loggerFor() + + // TODO: optimize table schema for InnoDB + private val createTableStatement = + "CREATE TABLE IF NOT EXISTS committed_states (" + + "issue_tx_id BINARY(32) NOT NULL," + + "issue_tx_output_id INT NOT NULL," + + "consuming_tx_id BINARY(32) NOT NULL," + + "consuming_tx_input_id INT UNSIGNED NOT NULL," + + "consuming_party_name TEXT NOT NULL," + + // TODO: do we need to store the key? X500 name should be sufficient + "consuming_party_key BLOB NOT NULL," + + "commit_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP," + + "CONSTRAINT id PRIMARY KEY (issue_tx_id, issue_tx_output_id)" + + ")" + private val insertStatement = "INSERT INTO committed_states (issue_tx_id, issue_tx_output_id, consuming_tx_id, consuming_tx_input_id, consuming_party_name, consuming_party_key) VALUES (?, ?, ?, ?, ?, ?)" + private val findStatement = "SELECT consuming_tx_id, consuming_tx_input_id, consuming_party_name, consuming_party_key FROM committed_states WHERE issue_tx_id = ? AND issue_tx_output_id = ?" + } + + private val metricPrefix = MySQLUniquenessProvider::class.simpleName + /** Transaction commit duration and rate metric timer */ + private val commitTimer = metrics.timer("$metricPrefix.Commit") + /** + * When writing to multiple masters with Galera, transaction rollbacks may happen due to high write contention. + * This is a useful heath metric. + */ + private val rollbackCounter = metrics.counter("$metricPrefix.Rollback") + /** Track double spend attempts. Note that this will also include notarisation retries. */ + private val conflictCounter = metrics.counter("$metricPrefix.Conflicts") + + val dataSource = HikariDataSource(HikariConfig(dataSourceProperties)) + + private val connection: Connection + get() = dataSource.connection + + fun createTable() { + log.debug("Attempting to create DB table if it does not yet exist: $createTableStatement") + connection.use { + it.createStatement().execute(createTableStatement) + it.commit() + } + } + + fun stop() { + dataSource.close() + } + + override fun commit(states: List, txId: SecureHash, callerIdentity: Party) { + val timer = commitTimer.time() + try { + retryTransaction(CommitAll(states, txId, callerIdentity)) + } catch (e: BatchUpdateException) { + log.info("Unable to commit input states, finding conflicts", e) + conflictCounter.inc() + retryTransaction(FindConflicts(states)) + } finally { + timer.stop() + } + } + + private fun retryTransaction(tx: RetryableTransaction) { + connection.use { + while (true) { + try { + tx.run(it) + } catch (e: Exception) { + it.rollback() + if (e is MySQLTransactionRollbackException) { + log.warn("Rollback exception occurred, retrying", e) + rollbackCounter.inc() + continue + } else { + throw e + } + } + break + } + it.commit() + } + } + + interface RetryableTransaction { + fun run(conn: Connection) + } + + private class CommitAll(val states: List, val txId: SecureHash, val callerIdentity: Party) : RetryableTransaction { + override fun run(conn: Connection) { + conn.prepareStatement(insertStatement).apply { + states.forEachIndexed { index, stateRef -> + // StateRef + setBytes(1, stateRef.txhash.bytes) + setInt(2, stateRef.index) + // Consuming transaction + setBytes(3, txId.bytes) + setInt(4, index) + setString(5, callerIdentity.name.toString()) + setBytes(6, callerIdentity.owningKey.serialize().bytes) + + addBatch() + clearParameters() + } + executeBatch() + close() + } + } + } + + private class FindConflicts(val states: List) : RetryableTransaction { + override fun run(conn: Connection) { + val conflicts = mutableMapOf() + states.forEach { + val st = conn.prepareStatement(findStatement).apply { + setBytes(1, it.txhash.bytes) + setInt(2, it.index) + } + val result = st.executeQuery() + + if (result.next()) { + val consumingTxId = SecureHash.SHA256(result.getBytes(1)) + val inputIndex = result.getInt(2) + val partyName = CordaX500Name.parse(result.getString(3)) + val partyKey: PublicKey = result.getBytes(4).deserialize() + conflicts[it] = UniquenessProvider.ConsumingTx(consumingTxId, inputIndex, Party(partyName, partyKey)) + } + } + conn.commit() + if (conflicts.isNotEmpty()) throw UniquenessException(UniquenessProvider.Conflict(conflicts)) + } + } +} \ No newline at end of file diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf index 9caf275e2f..7ce5aaa6b7 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -33,5 +33,4 @@ enterpriseConfiguration = { waitInterval = 40000 } } - -useAMQPBridges = true \ No newline at end of file +useAMQPBridges = true 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 5150d0883b..c39e9b8327 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 @@ -46,10 +46,8 @@ 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.TestConstants.*; 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; @@ -74,7 +72,8 @@ public class VaultQueryJavaTests { "net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName(), - DummyLinearStateSchemaV1.class.getPackage().getName()); + DummyLinearStateSchemaV1.class.getPackage().getName() + ); IdentityServiceInternal identitySvc = makeTestIdentityService(MEGA_CORP.getIdentity(), DUMMY_CASH_ISSUER_INFO.getIdentity(), DUMMY_NOTARY.getIdentity()); Pair databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages, 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 0a711088b8..4e750992dc 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 @@ -13,11 +13,12 @@ import net.corda.node.services.statemachine.FlowStart 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.ALICE_NAME import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.TestIdentity +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 import org.junit.Before 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 dc412846a6..82a1e47e9a 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 @@ -24,26 +24,26 @@ import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.DummyFungibleContract -import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.sampleschemas.SampleCashSchemaV2 import net.corda.finance.sampleschemas.SampleCashSchemaV3 +import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.utils.sumCash +import net.corda.node.internal.configureDatabase +import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.vault.VaultSchemaV1 -import net.corda.node.internal.configureDatabase -import net.corda.node.services.api.IdentityServiceInternal 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.internal.rigorousMock import net.corda.testing.internal.vault.DummyDealStateSchemaV1 +import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 +import net.corda.testing.internal.vault.DummyLinearStateSchemaV2 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.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 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 70430f4f16..fc78e3354b 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 @@ -1,6 +1,9 @@ package net.corda.node.services.vault -import net.corda.core.contracts.* +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.InsufficientBalanceException +import net.corda.core.contracts.LinearState +import net.corda.core.contracts.UniqueIdentifier import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name @@ -42,7 +45,7 @@ import kotlin.test.fail class VaultWithCashTest { private companion object { private val cordappPackages = listOf( - "net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName, DummyLinearStateSchemaV1::class.packageName) + "net.corda.testing.contracts", "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) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt index 777769d03f..29cd2e270c 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt @@ -4,18 +4,17 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.* +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.DUMMY_CASH_ISSUER_KEY +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.STATE import net.corda.core.contracts.* -import net.corda.core.crypto.generateKeyPair -import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.days -import net.corda.core.utilities.seconds import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.dsl.EnforceVerifyOrFail @@ -24,16 +23,12 @@ import net.corda.testing.dsl.TransactionDSLInterpreter import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.ledger -import net.corda.testing.node.makeTestIdentityService import net.corda.testing.node.transaction import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import java.time.Instant import java.util.* -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue // TODO: The generate functions aren't tested by these tests: add them. @@ -47,8 +42,7 @@ interface CommercialPaperTestTemplate { 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.publicKey +private val MEGA_CORP_PUBKEY get() = megaCorp.keyPair.public class KotlinCommercialPaperTest : CommercialPaperTestTemplate { @@ -85,21 +79,14 @@ class CommercialPaperTestsGeneric { @Parameterized.Parameters @JvmStatic fun data() = listOf(KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest()) - private val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10) - private val DUMMY_CASH_ISSUER_IDENTITY get() = dummyCashIssuer.identity - private val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1) private val alice = TestIdentity(ALICE_NAME, 70) - private val BIG_CORP_KEY = generateKeyPair() 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.keyPair - private val ALICE_PUBKEY get() = alice.publicKey + private val ALICE_PUBKEY get() = alice.keyPair.public 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.publicKey + private val MINI_CORP_PUBKEY get() = miniCorp.keyPair.public } @@ -159,8 +146,10 @@ class CommercialPaperTestsGeneric { output(Cash.PROGRAM_ID, "Alice's profit", aliceGetsBack.STATE ownedBy ALICE) output(Cash.PROGRAM_ID, "Change", (someProfits - aliceGetsBack).STATE ownedBy MEGA_CORP) } + command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) command(ALICE_PUBKEY, thisTest.getRedeemCommand(DUMMY_NOTARY)) + tweak { outputs(700.DOLLARS `issued by` issuer) timeWindow(TEST_TX_TIME + 8.days) @@ -252,96 +241,7 @@ class CommercialPaperTestsGeneric { private lateinit var alicesVault: Vault 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 val issuerServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, DUMMY_CASH_ISSUER_KEY) private lateinit var moveTX: SignedTransaction - - @Test - fun `issue move and then redeem`() { - val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices( - 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 - - databaseAlice.transaction { - alicesVault = aliceServices.fillWithSomeTestCash(9000.DOLLARS, issuerServices, issuedBy = DUMMY_CASH_ISSUER, outputNotary = DUMMY_NOTARY) - aliceVaultService = aliceServices.vaultService - } - - val bigCorpDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices( - 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 - - databaseBigCorp.transaction { - bigCorpVault = bigCorpServices.fillWithSomeTestCash(13000.DOLLARS, issuerServices, issuedBy = DUMMY_CASH_ISSUER, outputNotary = DUMMY_NOTARY) - bigCorpVaultService = bigCorpServices.vaultService - } - - // Propagate the cash transactions to each side. - aliceServices.recordTransactions(bigCorpVault.states.map { bigCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! }) - bigCorpServices.recordTransactions(alicesVault.states.map { aliceServices.validatedTransactions.getTransaction(it.ref.txhash)!! }) - - // BigCorpâ„¢ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself. - val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER - val issuance = bigCorpServices.myInfo.chooseIdentity().ref(1) - val issueBuilder = CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY) - issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds) - val issuePtx = bigCorpServices.signInitialTransaction(issueBuilder) - val issueTx = notaryServices.addSignature(issuePtx) - - databaseAlice.transaction { - // 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(BIG_CORP_KEY.public)) - CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(ALICE_PUBKEY)) - val ptx = aliceServices.signInitialTransaction(builder) - val ptx2 = bigCorpServices.addSignature(ptx) - val stx = notaryServices.addSignature(ptx2) - stx - } - } - - databaseBigCorp.transaction { - // Verify the txns are valid and insert into both sides. - listOf(issueTx, moveTX).forEach { - it.toLedgerTransaction(aliceServices).verify() - aliceServices.recordTransactions(it) - bigCorpServices.recordTransactions(it) - } - } - - databaseBigCorp.transaction { - fun makeRedeemTX(time: Instant): Pair { - val builder = TransactionBuilder(DUMMY_NOTARY) - builder.setTimeWindow(time, 30.seconds) - CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpServices, bigCorpServices.myInfo.chooseIdentityAndCert()) - val ptx = aliceServices.signInitialTransaction(builder) - val ptx2 = bigCorpServices.addSignature(ptx) - val stx = notaryServices.addSignature(ptx2) - return Pair(stx, builder.lockId) - } - - val redeemTX = makeRedeemTX(TEST_TX_TIME + 10.days) - val tooEarlyRedemption = redeemTX.first - val tooEarlyRedemptionLockId = redeemTX.second - val e = assertFailsWith(TransactionVerificationException::class) { - tooEarlyRedemption.toLedgerTransaction(aliceServices).verify() - } - // manually release locks held by this failing transaction - aliceServices.vaultService.softLockRelease(tooEarlyRedemptionLockId) - assertTrue(e.cause!!.message!!.contains("paper must have matured")) - - val validRedemption = makeRedeemTX(TEST_TX_TIME + 31.days).first - validRedemption.toLedgerTransaction(aliceServices).verify() - // soft lock not released after success either!!! (as transaction not recorded) - } - } } diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index a9dec35c86..9685dabe55 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -137,13 +137,19 @@ class CashTests { @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) - megaCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) - miniCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock().also { - doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) - }, MINI_CORP.name, MINI_CORP_KEY) - val notaryServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - val databaseAndServices = makeTestDatabaseAndMockServices( + megaCorpServices = MockServices( listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), + rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) + miniCorpServices = MockServices( + listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), + rigorousMock().also { + doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) + }, MINI_CORP.name, MINI_CORP_KEY) + val notaryServices = MockServices( + listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), + rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + val databaseAndServices = makeTestDatabaseAndMockServices( + listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), TestIdentity(CordaX500Name("Me", "London", "GB"))) database = databaseAndServices.first @@ -879,7 +885,7 @@ class CashTests { transaction { attachment(Cash.PROGRAM_ID) input("MEGA_CORP cash") - // We send it to another publicKey so that the transaction is not identical to the previous one + // We send it to another pubkey so that the transaction is not identical to the previous one output(Cash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output().copy(owner = ALICE)) command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) this.verifies() diff --git a/settings.gradle b/settings.gradle index 408fd292e7..6cbb77c684 100644 --- a/settings.gradle +++ b/settings.gradle @@ -51,6 +51,9 @@ include 'samples:notary-demo' include 'samples:bank-of-corda-demo' include 'samples:business-network-demo' include 'cordform-common' +include 'network-management' +include 'network-management:capsule' +include 'network-management:capsule-hsm' include 'verify-enclave' include 'hsm-tool' project(':hsm-tool').with { 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 815d1e0496..4ccf3ab00a 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 @@ -268,8 +268,7 @@ class InMemoryMessagingNetwork internal constructor( private val peerHandle: PeerHandle, private val executor: AffinityExecutor, private val database: CordaPersistence) : SingletonSerializeAsToken(), MessagingService { - private inner class Handler(val topicSession: String, - val callback: MessageHandler) : MessageHandlerRegistration + inner 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/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index c4f7c25d72..37b6c80521 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 @@ -398,7 +398,7 @@ class MockNetwork(private val cordappPackages: List, val config = mockNodeConfiguration().also { doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName - doReturn(makeTestDataSourceProperties("node_$id", "net_$networkId")).whenever(it).dataSourceProperties + doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties doReturn(makeTestDatabaseProperties("node_$id")).whenever(it).database parameters.configOverrides(it) } @@ -503,6 +503,5 @@ private fun mockNodeConfiguration(): NodeConfiguration { doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec doReturn(null).whenever(it).devModeOptions doReturn(true).whenever(it).useAMQPBridges - doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index b4dd36315a..97bacf3b9a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -15,7 +15,9 @@ import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices -import net.corda.testing.* +import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.TestIdentity +import net.corda.testing.chooseIdentity import net.corda.testing.dsl.* /** diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 4a585a61e6..dae927c366 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -39,6 +39,9 @@ dependencies { runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_config', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'ApacheJMeter', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'jorphan', version: "$jmVersion" + //For Azure SQL and SQL Server support in persistence + runtime group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: '6.2.1.jre8' + testCompile project(':test-utils') testCompile project(':node-driver') diff --git a/tools/jmeter/src/main/resources/Testplans/SQL.jmx b/tools/jmeter/src/main/resources/Testplans/SQL.jmx new file mode 100644 index 0000000000..0014ad8f62 --- /dev/null +++ b/tools/jmeter/src/main/resources/Testplans/SQL.jmx @@ -0,0 +1,244 @@ + + + + + + false + false + + + + + + + + continue + + false + 40000 + + 3 + 1 + 1509455820000 + 1509455820000 + false + + + + + + true + + 5000 + testpool + jdbc:sqlserver://perfperformancetest.database.windows.net:1433;databaseName=perftesting + com.microsoft.sqlserver.jdbc.SQLServerDriver + true + yourStrong(!)Password + 0 + 10000 + TRANSACTION_REPEATABLE_READ + 60000 + perfnode1@perfperformancetest + + + + testpool + select dbtransact0_.tx_id as tx_id1_22_0_, dbtransact0_.transaction_value as transact2_22_0_ from perfnode1.node_transactions dbtransact0_ where dbtransact0_.tx_id='${__UUID()}' + + + + Select Statement + Store as String + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + + + + diff --git a/tools/jmeter/src/main/resources/Testplans/SQL_parmeterized.jmx b/tools/jmeter/src/main/resources/Testplans/SQL_parmeterized.jmx new file mode 100644 index 0000000000..e287233d2e --- /dev/null +++ b/tools/jmeter/src/main/resources/Testplans/SQL_parmeterized.jmx @@ -0,0 +1,246 @@ + + + + + + false + false + + + + + + + + true + + 5000 + testpool + jdbc:sqlserver://perfperformancetest.database.windows.net:1433;databaseName=perftesting + com.microsoft.sqlserver.jdbc.SQLServerDriver + true + yourStrong(!)Password + 0 + 10000 + TRANSACTION_REPEATABLE_READ + 60000 + perfnode1@perfperformancetest + + + + continue + + false + 40000 + + 3 + 1 + 1509455820000 + 1509455820000 + false + + + + + + testpool + declare @sql nvarchar(4000) +set @sql = N'select dbtransact0_.tx_id as tx_id1_22_0_, dbtransact0_.transaction_value as transact2_22_0_ from perfnode1.node_transactions dbtransact0_ where dbtransact0_.tx_id=@P0' +exec sp_executesql @sql, N'@P0 nvarchar(4000)', '${__UUID()}' + + + + Select Statement + Store as String + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + + + + diff --git a/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt b/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt index 81699c31a9..c3d33fa536 100644 --- a/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt +++ b/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt @@ -34,14 +34,13 @@ class NativeSgxApiTest { val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val MEGA_CORP get() = megaCorp.party - val MEGA_CORP_PUBKEY get() = megaCorp.publicKey - val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).publicKey + val MEGA_CORP_PUBKEY get() = megaCorp.keyPair.public + val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).keyPair.public } - private val identityService = rigorousMock().also { - doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - } - private val ledgerServices = MockServices(emptyList(), identityService, MEGA_CORP.name) + private val ledgerServices = MockServices(emptyList(), rigorousMock().also { + doReturn(NativeSgxApiTest.MEGA_CORP).whenever(it).partyFromKey(NativeSgxApiTest.MEGA_CORP_PUBKEY) + }, NativeSgxApiTest.MEGA_CORP.name) @Ignore("The SGX code is not part of the standard build yet") @Test diff --git a/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt b/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt index 066d5b94ee..4a633f0841 100644 --- a/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt +++ b/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt @@ -32,17 +32,16 @@ class EnclaveletTest { val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val MEGA_CORP get() = megaCorp.party - val MEGA_CORP_PUBKEY get() = megaCorp.publicKey - val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).publicKey + val MEGA_CORP_PUBKEY get() = megaCorp.keyPair.public + val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).keyPair.public } @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - private val identityService = rigorousMock().also { + private val ledgerServices = MockServices(emptyList(), rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - } - private val ledgerServices = MockServices(emptyList(), identityService, MEGA_CORP.name) + }, MEGA_CORP.name) @Ignore("Pending Gradle bug: https://github.com/gradle/gradle/issues/2657") @Test From de196aa5f059133fb69fe9db033afe7d8d8cf039 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 15:39:20 +0000 Subject: [PATCH 36/44] Fix broken MySQLNotaryServiceTests tests. --- .../kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt index f9e1f16b61..f1f467d24e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt @@ -14,9 +14,9 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.config.NotaryConfig -import net.corda.nodeapi.internal.NetworkParametersCopier -import net.corda.nodeapi.internal.NotaryInfo import net.corda.nodeapi.internal.ServiceIdentityGenerator +import net.corda.nodeapi.internal.network.NetworkParametersCopier +import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.testing.* import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract From e6ad302ea3e0b992811eeb23a04be6c544b1014c Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 15:48:26 +0000 Subject: [PATCH 37/44] Re-instate main gradle build configuration removed by exfalso. --- build.gradle | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/build.gradle b/build.gradle index 98a82c7fc2..2da8daa2e8 100644 --- a/build.gradle +++ b/build.gradle @@ -162,6 +162,33 @@ allprojects { tasks.withType(Test) { // Prevent the project from creating temporary files outside of the build directory. systemProperties['java.io.tmpdir'] = buildDir + + // Ensures that "net.corda.testing.amqp.enable" is passed correctly from Gradle command line + // down to JVM executing unit test. It looks like we are running unit tests in the forked mode + // and all the "-D" parameters passed to Gradle not making it to unit test level + // TODO: Remove once we fully switched to AMQP + final AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable" + systemProperty(AMQP_ENABLE_PROP_NAME, System.getProperty(AMQP_ENABLE_PROP_NAME)) + + // relational database provider to be used by node + final DATABASE_PROVIDER = "databaseProvider" + final DATASOURCE_URL = "dataSourceProperties.dataSource.url" + final DATASOURCE_CLASSNAME = "dataSourceProperties.dataSourceClassName" + final DATASOURCE_USER = "dataSourceProperties.dataSource.user" + final DATASOURCE_PASSWORD = "dataSourceProperties.dataSource.password" + + // integration testing database configuration (to be used in conjunction with a DATABASE_PROVIDER) + final TEST_DB_ADMIN_USER = "test.db.admin.user" + final TEST_DB_ADMIN_PASSWORD = "test.db.admin.password" + final TEST_DB_SCRIPT_DIR = "test.db.script.dir" + + [DATABASE_PROVIDER,DATASOURCE_URL, DATASOURCE_CLASSNAME, DATASOURCE_USER, DATASOURCE_PASSWORD, + TEST_DB_ADMIN_USER, TEST_DB_ADMIN_PASSWORD, TEST_DB_SCRIPT_DIR].forEach { + def property = System.getProperty(it) + if (property != null) { + systemProperty(it, property) + } + } } group 'com.r3.corda.enterprise' From 9959359e9283a7a25b6fa706958aa8a93f3296eb Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 16:01:00 +0000 Subject: [PATCH 38/44] Re-instate previous commit fix removed by exfalso. --- .../src/main/kotlin/net/corda/testing/node/MockNode.kt | 1 + 1 file changed, 1 insertion(+) 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 37b6c80521..34cd93d367 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 @@ -503,5 +503,6 @@ private fun mockNodeConfiguration(): NodeConfiguration { doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec doReturn(null).whenever(it).devModeOptions doReturn(true).whenever(it).useAMQPBridges + doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration } } From 8ef3f1a6a0168d1e2c8ee625c3eae9ee69efc939 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 18 Dec 2017 16:14:36 +0000 Subject: [PATCH 39/44] fix BFTNotoaryServiceTests --- .../kotlin/net/corda/node/services/BFTNotaryServiceTests.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 3c65d0e2a2..869ce4a6ea 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 @@ -43,13 +43,12 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class BFTNotaryServiceTests { - private lateinit var mockNet: MockNetwork + private val mockNet = MockNetwork(emptyList()) private lateinit var notary: Party private lateinit var node: StartedNode @Before fun before() { - mockNet = MockNetwork(emptyList()) node = mockNet.createNode() } @After From 38b0ed4a3589cd0518c79f1887780168307eb9ca Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 18 Dec 2017 16:33:08 +0000 Subject: [PATCH 40/44] IntegrationTest() --- .../corda/node/services/BFTNotaryServiceTests.kt | 10 +++++++++- .../node/services/network/NetworkMapTest.kt | 16 ++++++++-------- .../registration/NodeRegistrationTest.kt | 12 +++++++----- 3 files changed, 24 insertions(+), 14 deletions(-) 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 869ce4a6ea..aaf2cb5e04 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 @@ -27,6 +27,8 @@ import net.corda.node.services.transactions.minCorrectReplicas import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.testing.IntegrationTest +import net.corda.testing.IntegrationTestSchemas import net.corda.testing.chooseIdentity import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract @@ -37,12 +39,18 @@ import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before +import org.junit.ClassRule import org.junit.Test import java.nio.file.Paths import kotlin.test.assertEquals import kotlin.test.assertTrue -class BFTNotaryServiceTests { +class BFTNotaryServiceTests : IntegrationTest() { + companion object { + @ClassRule @JvmField + val databaseSchemas = IntegrationTestSchemas("node_0", "node_1", "node_2", "node_3", "node_4", "node_5", + "node_6", "node_7", "node_8", "node_9") + } private val mockNet = MockNetwork(emptyList()) private lateinit var notary: Party private lateinit var node: StartedNode 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 a46ae6f378..67c14407c5 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 @@ -9,23 +9,23 @@ 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.SerializationEnvironmentRule +import net.corda.testing.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation import net.corda.testing.node.internal.CompatibilityZoneParams import net.corda.testing.node.internal.internalDriver 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.Rule -import org.junit.Test +import org.junit.* import java.net.URL import kotlin.test.assertEquals -class NetworkMapTest { +class NetworkMapTest : IntegrationTest() { + companion object { + @ClassRule @JvmField + val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName(), + DUMMY_NOTARY_NAME.toDatabaseSchemaName()) + } @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) 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 bfc30a96d7..cb955b7be0 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 @@ -15,6 +15,7 @@ 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.IntegrationTest +import net.corda.testing.IntegrationTestSchemas import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation import net.corda.testing.node.internal.CompatibilityZoneParams @@ -24,10 +25,7 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.* import java.io.ByteArrayOutputStream import java.io.InputStream import java.net.URL @@ -40,7 +38,11 @@ import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response -class NodeRegistrationTest { +class NodeRegistrationTest : IntegrationTest() { + companion object { + @ClassRule @JvmField + val databaseSchemas = IntegrationTestSchemas("Alice") + } @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) From 4175593b18630eac032c2344519b4e4522171e49 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 18 Dec 2017 16:40:54 +0000 Subject: [PATCH 41/44] remove :network-management --- settings.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/settings.gradle b/settings.gradle index 6cbb77c684..408fd292e7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -51,9 +51,6 @@ include 'samples:notary-demo' include 'samples:bank-of-corda-demo' include 'samples:business-network-demo' include 'cordform-common' -include 'network-management' -include 'network-management:capsule' -include 'network-management:capsule-hsm' include 'verify-enclave' include 'hsm-tool' project(':hsm-tool').with { From 344e005b30f67f9a6f8bff1ced084452173a86b4 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 18 Dec 2017 16:53:14 +0000 Subject: [PATCH 42/44] ignore nack test --- .../kotlin/net/corda/node/amqp/AMQPBridgeTest.kt | 2 ++ 1 file changed, 2 insertions(+) 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 index 4c2f0ee881..0ab3dc6ff4 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -22,6 +22,7 @@ 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.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -47,6 +48,7 @@ class AMQPBridgeTest { private abstract class AbstractNodeConfiguration : NodeConfiguration + @Ignore @Test fun `test acked and nacked messages`() { // Create local queue From b2ecb2e6718a0dd9ab7481937d095cd57830e2cc Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 16:53:44 +0000 Subject: [PATCH 43/44] Ignore non-deterministic Bridge ack/nak test (failing on TC) --- .../kotlin/net/corda/node/amqp/AMQPBridgeTest.kt | 3 +++ 1 file changed, 3 insertions(+) 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 index 4c2f0ee881..32d43cb5e3 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -22,6 +22,7 @@ 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.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -47,6 +48,8 @@ class AMQPBridgeTest { private abstract class AbstractNodeConfiguration : NodeConfiguration + // TODO: revisit upon Matthew Nesbitt return + @Ignore() @Test fun `test acked and nacked messages`() { // Create local queue From f55e565a3d86de1b62e59a40d64b3aa10973ad30 Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 18 Dec 2017 17:01:05 +0000 Subject: [PATCH 44/44] Re-instate previous commit fix removed by exfalso. --- .../contracts/asset/CashTests.kt | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index 9685dabe55..be5f780685 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -137,19 +137,13 @@ class CashTests { @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) - megaCorpServices = MockServices( - listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), - rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) - miniCorpServices = MockServices( - listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), - rigorousMock().also { - doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) - }, MINI_CORP.name, MINI_CORP_KEY) - val notaryServices = MockServices( - listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), - rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + megaCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) + miniCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock().also { + doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) + }, MINI_CORP.name, MINI_CORP_KEY) + val notaryServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val databaseAndServices = makeTestDatabaseAndMockServices( - listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), + listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), TestIdentity(CordaX500Name("Me", "London", "GB"))) database = databaseAndServices.first