diff --git a/.ci/api-current.txt b/.ci/api-current.txt index aeea99f4bb..8b80be5e32 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -3228,7 +3228,7 @@ public static final class net.corda.core.transactions.FilteredTransaction$Compan @org.jetbrains.annotations.NotNull public final List inputsOfType(Class) public String toString() public final void verify() - public static final net.corda.core.transactions.LedgerTransaction$Companion Companion + @java.lang.Deprecated public static final net.corda.core.transactions.LedgerTransaction$Companion Companion ## public static final class net.corda.core.transactions.LedgerTransaction$InOutGroup extends java.lang.Object public (List, List, Object) @@ -3334,7 +3334,7 @@ public static final class net.corda.core.transactions.NotaryChangeWireTransactio @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignature(java.security.KeyPair, net.corda.core.crypto.SignatureMetadata) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignature(net.corda.core.crypto.TransactionSignature) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignatures(Iterable) - public static final net.corda.core.transactions.SignedTransaction$Companion Companion + @java.lang.Deprecated public static final net.corda.core.transactions.SignedTransaction$Companion Companion ## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash public (Set, List, net.corda.core.crypto.SecureHash) diff --git a/build.gradle b/build.gradle index e4881f573e..f4f268ebef 100644 --- a/build.gradle +++ b/build.gradle @@ -183,8 +183,8 @@ allprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { - languageVersion = "1.1" - apiVersion = "1.1" + languageVersion = "1.2" + apiVersion = "1.2" jvmTarget = "1.8" javaParameters = true // Useful for reflection. } diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index a7cffcb48b..9943a9f750 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -474,3 +474,8 @@ fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationR } val PublicKey.hash: SecureHash get() = encoded.sha256() + +/** + * Extension method for providing a sumBy method that processes and returns a Long + */ +fun Iterable.sumByLong(selector: (T) -> Long): Long = this.map { selector(it) }.sum() diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index 563b97739d..fe4ec4362c 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -140,7 +140,3 @@ fun Future.getOrThrow(timeout: Duration? = null): V = try { throw e.cause!! } -/** - * Extension method for providing a sumBy method that processes and returns a Long - */ -fun Iterable.sumByLong(selector: (T) -> Long): Long = this.map { selector(it) }.sum() 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 fef3a8e00a..4eb8bfb3d3 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -11,6 +11,7 @@ package net.corda.core.flows; import co.paralleluniverse.fibers.Suspendable; +import com.google.common.collect.ImmutableList; import com.google.common.primitives.Primitives; import net.corda.core.identity.Party; import net.corda.testing.core.TestConstants; @@ -23,13 +24,12 @@ 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.core.TestUtils.singleIdentity; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.Assert.fail; public class FlowsInJavaTest { - private final MockNetwork mockNet = new MockNetwork(emptyList()); + private final MockNetwork mockNet = new MockNetwork(ImmutableList.of("net.corda.core.flows")); private StartedMockNode aliceNode; private StartedMockNode bobNode; private Party bob; @@ -48,7 +48,6 @@ public class FlowsInJavaTest { @Test public void suspendableActionInsideUnwrap() throws Exception { - bobNode.registerInitiatedFlow(SendHelloAndThenReceive.class); Future result = aliceNode.startFlow(new SendInUnwrapFlow(bob)); mockNet.runNetwork(); assertThat(result.get()).isEqualTo("Hello"); 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 4e8bec09ed..febe2315d2 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -51,7 +51,7 @@ class CollectSignaturesFlowTests { @Before fun setup() { - mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows")) aliceNode = mockNet.createPartyNode(ALICE_NAME) bobNode = mockNet.createPartyNode(BOB_NAME) charlieNode = mockNet.createPartyNode(CHARLIE_NAME) @@ -66,12 +66,6 @@ class CollectSignaturesFlowTests { mockNet.stopNodes() } - private fun registerFlowOnAllNodes(flowClass: KClass>) { - listOf(aliceNode, bobNode, charlieNode).forEach { - it.registerInitiatedFlow(flowClass.java) - } - } - // With this flow, the initiator starts the "CollectTransactionFlow". It is then the responders responsibility to // override "checkTransaction" and add whatever logic their require to verify the SignedTransaction they are // receiving off the wire. @@ -120,7 +114,6 @@ class CollectSignaturesFlowTests { // Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity aliceNode.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity) } - registerFlowOnAllNodes(TestFlow.Responder::class) val magicNumber = 1337 val parties = listOf(alice, bConfidentialIdentity.party, charlie) val state = DummyContract.MultiOwnerState(magicNumber, parties) diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index 8b1c5e465a..fa2e9c80e2 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -48,12 +48,10 @@ class ResolveTransactionsFlowTest { @Before fun setup() { - mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.internal")) notaryNode = mockNet.defaultNotaryNode megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB")) miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB")) - megaCorpNode.registerInitiatedFlow(TestResponseFlow::class.java) - miniCorpNode.registerInitiatedFlow(TestResponseFlow::class.java) notary = mockNet.defaultNotaryIdentity megaCorp = megaCorpNode.info.singleIdentity() miniCorp = miniCorpNode.info.singleIdentity() diff --git a/docs/source/api-testing.rst b/docs/source/api-testing.rst index 6a04804b88..b2662c1acf 100644 --- a/docs/source/api-testing.rst +++ b/docs/source/api-testing.rst @@ -165,24 +165,6 @@ Nodes are created on the ``MockNetwork`` using: } } -Registering a node's initiated flows -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Regular Corda nodes automatically register any response flows defined in their installed CorDapps. When using a -``MockNetwork``, each ``StartedMockNode`` must manually register any responder flows it wishes to use. - -Responder flows are registered as follows: - -.. container:: codeset - - .. sourcecode:: kotlin - - nodeA.registerInitiatedFlow(ExampleFlow.Acceptor::class.java) - - .. sourcecode:: java - - nodeA.registerInitiatedFlow(ExampleFlow.Acceptor.class); - Running the network ^^^^^^^^^^^^^^^^^^^ 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 c9900fc9e6..21253c4aa9 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 @@ -11,6 +11,7 @@ package net.corda.docs.tutorial.mocknetwork import co.paralleluniverse.fibers.Suspendable +import com.google.common.collect.ImmutableList import net.corda.core.contracts.requireThat import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession @@ -72,10 +73,9 @@ class TutorialMockNetwork { @Before fun setUp() { - mockNet = MockNetwork(emptyList()) + mockNet = MockNetwork(ImmutableList.of("net.corda.docs.tutorial.mocknetwork")) nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() - nodeB.registerInitiatedFlow(FlowB::class.java) } @After diff --git a/docs/source/flow-testing.rst b/docs/source/flow-testing.rst index df7844dd44..9d36351c63 100644 --- a/docs/source/flow-testing.rst +++ b/docs/source/flow-testing.rst @@ -61,10 +61,6 @@ transactions are valid) inside a ``database.transaction``. All node flows run w nodes themselves, but any time we need to use the database directly from a unit test, you need to provide a database transaction as shown here. -With regards to initiated flows (see :doc:`flow-state-machines` for information on initiated and initiating flows), the -full node automatically registers them by scanning the CorDapp jars. In a unit test environment this is not possible so -``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow. - .. MockNetwork message manipulation .. -------------------------------- .. The MockNetwork has the ability to manipulate message streams. You can use this to test your flows behaviour on corrupted, diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt b/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt index 875a1d3559..799cdaa480 100644 --- a/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt +++ b/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt @@ -15,6 +15,7 @@ import net.corda.behave.file.currentDirectory import net.corda.behave.logging.getLogger import net.corda.behave.process.output.OutputListener import rx.Observable +import rx.Subscriber import java.io.Closeable import java.io.File import java.io.IOException @@ -153,8 +154,9 @@ open class Command( return exitCode } - fun use(action: (Command, Observable) -> Unit = { _, _ -> }): Int { + fun use(subscriber: Subscriber, action: (Command, Observable) -> Unit = { _, _ -> }): Int { try { + output.subscribe(subscriber) start() action(this, output) } finally { diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt deleted file mode 100644 index f3fad092c9..0000000000 --- a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt +++ /dev/null @@ -1,18 +0,0 @@ -package net.corda.behave.scenarios - -import cucumber.api.java.After -import cucumber.api.java.Before - -@Suppress("KDocMissingDocumentation") -class ScenarioHooks(private val state: ScenarioState) { - - @Before - fun beforeScenario() { - } - - @After - fun afterScenario() { - state.stopNetwork() - } - -} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/VaultSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/VaultSteps.kt index 4a2b7b3c3c..e5249a2bb2 100644 --- a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/VaultSteps.kt +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/VaultSteps.kt @@ -4,7 +4,7 @@ import net.corda.behave.scenarios.ScenarioState import net.corda.behave.scenarios.api.StepsBlock import net.corda.behave.scenarios.helpers.Vault import net.corda.core.contracts.ContractState -import net.corda.core.utilities.sumByLong +import net.corda.core.internal.sumByLong import net.corda.finance.contracts.asset.Cash class VaultSteps : StepsBlock { diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt index dd4f015946..55f565f817 100644 --- a/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt +++ b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt @@ -31,8 +31,7 @@ class CommandTests { @Test fun `output stream for command can be observed`() { val subscriber = TestSubscriber() - val exitCode = Command(listOf("ls", "/")).use { _, output -> - output.subscribe(subscriber) + val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, output -> subscriber.awaitTerminalEvent() subscriber.assertCompleted() subscriber.assertNoErrors() diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/service/PostreSQLServiceTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/service/PostreSQLServiceTests.kt index eb7b659a33..e69de29bb2 100644 --- a/experimental/behave/src/test/kotlin/net/corda/behave/service/PostreSQLServiceTests.kt +++ b/experimental/behave/src/test/kotlin/net/corda/behave/service/PostreSQLServiceTests.kt @@ -1,29 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package net.corda.behave.service - -import net.corda.behave.service.database.PostgreSQLService -import org.assertj.core.api.Assertions.assertThat -import org.junit.Ignore -import org.junit.Test - -class PostreSQLServiceTests { - - @Ignore - @Test - fun `postgres can be started and stopped`() { - val service = PostgreSQLService("test-postgres", 12345, "postgres") - val didStart = service.start() - service.stop() - assertThat(didStart).isTrue() - } - -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt index 484bd1ce27..dcb4780708 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt @@ -13,40 +13,53 @@ package net.corda.nodeapi.internal import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import net.corda.core.contracts.Contract import net.corda.core.contracts.ContractClassName +import net.corda.core.contracts.UpgradedContract +import net.corda.core.contracts.UpgradedContractWithLegacyConstraint import net.corda.core.internal.copyTo import net.corda.core.internal.deleteIfExists +import net.corda.core.internal.logElapsedTime import net.corda.core.internal.read +import org.slf4j.LoggerFactory import java.io.InputStream import java.lang.reflect.Modifier import java.net.URLClassLoader import java.nio.file.Files import java.nio.file.Path -import java.nio.file.Paths import java.nio.file.StandardCopyOption +import java.util.Collections.singleton + +// When scanning of the CorDapp Jar is performed without "corda-core.jar" being the in the classpath, there is no way to appreciate +// relationships between those interfaces, therefore they have to be listed explicitly. +val coreContractClasses = setOf(Contract::class, UpgradedContractWithLegacyConstraint::class, UpgradedContract::class) /** * Scans the jar for contracts. * @returns: found contract class names or null if none found */ fun scanJarForContracts(cordappJar: Path): List { - val currentClassLoader = Contract::class.java.classLoader val scanResult = FastClasspathScanner() - .addClassLoader(currentClassLoader) - .overrideClasspath(cordappJar, Paths.get(Contract::class.java.protectionDomain.codeSource.location.toURI())) + // A set of a single element may look odd, but if this is removed "Path" which itself is an `Iterable` + // is getting broken into pieces to scan individually, which doesn't yield desired effect. + .overrideClasspath(singleton(cordappJar)) .scan() - val contracts = (scanResult.getNamesOfClassesImplementing(Contract::class.qualifiedName) ).distinct() + val contracts = coreContractClasses.flatMap { contractClass -> scanResult.getNamesOfClassesImplementing(contractClass.qualifiedName) }.distinct() // Only keep instantiable contracts - return URLClassLoader(arrayOf(cordappJar.toUri().toURL()), currentClassLoader).use { + return URLClassLoader(arrayOf(cordappJar.toUri().toURL()), Contract::class.java.classLoader).use { contracts.map(it::loadClass).filter { !it.isInterface && !Modifier.isAbstract(it.modifiers) } }.map { it.name } } +private val logger = LoggerFactory.getLogger("ClassloaderUtils") + fun withContractsInJar(jarInputStream: InputStream, withContracts: (List, InputStream) -> T): T { val tempFile = Files.createTempFile("attachment", ".jar") try { jarInputStream.copyTo(tempFile, StandardCopyOption.REPLACE_EXISTING) - val contracts = scanJarForContracts(tempFile.toAbsolutePath()) + val cordappJar = tempFile.toAbsolutePath() + val contracts = logElapsedTime("Contracts loading for '$cordappJar'", logger) { + scanJarForContracts(cordappJar) + } return tempFile.read { withContracts(contracts, it) } } finally { tempFile.deleteIfExists() diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 3eef29d5f6..68ab744275 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -27,6 +27,7 @@ import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration +import net.corda.nodeapi.internal.coreContractClasses import net.corda.nodeapi.internal.serialization.DefaultWhitelist import org.apache.commons.collections4.map.LRUMap import java.io.File @@ -251,13 +252,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List { - return (scanResult.getNamesOfClassesImplementing(Contract::class) + - scanResult.getNamesOfClassesImplementing(UpgradedContract::class) + - // Even though UpgradedContractWithLegacyConstraint implements UpgradedContract - // we need to specify it separately. Otherwise, classes implementing UpgradedContractWithLegacyConstraint - // don't get picked up. - scanResult.getNamesOfClassesImplementing(UpgradedContractWithLegacyConstraint::class)) - .distinct() + return coreContractClasses.flatMap { scanResult.getNamesOfClassesImplementing(it) }.distinct() } private fun findPlugins(cordappJarPath: RestrictedURL): List { diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index a123337b7b..3dcbe391e0 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -59,7 +59,6 @@ class TraderDemoTest : IntegrationTest() { startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser)), startNode(providedName = BOC_NAME, rpcUsers = listOf(bankUser)) ).map { (it.getOrThrow() as InProcess) } - nodeA.registerInitiatedFlow(BuyerFlow::class.java) val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map { val client = CordaRPCClient(it.rpcAddress) client.start(demoUser.username, demoUser.password).proxy