diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 952517db9a..f4435303d4 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -6505,10 +6505,6 @@ public final class net.corda.testing.node.NotarySpec extends java.lang.Object public int hashCode() public String toString() ## -public interface net.corda.testing.node.ResponderFlowFactory - @NotNull - public abstract F invoke(net.corda.core.flows.FlowSession) -## public final class net.corda.testing.node.StartedMockNode extends java.lang.Object @NotNull public final java.util.List>> findStateMachines(Class) diff --git a/.ci/check-api-changes.sh b/.ci/check-api-changes.sh index 2b01bfb74a..d79841d4e5 100755 --- a/.ci/check-api-changes.sh +++ b/.ci/check-api-changes.sh @@ -44,10 +44,12 @@ $newAbstracts EOF ` -#Get a list of any methods that expose classes in .internal. namespaces, and any classes which extend/implement -#an internal class +# Get a list of any methods that expose internal classes, which includes: +# - classes in .internal. namespaces (excluding the kotlin.jvm.internal namespace) +# - classes which extend/implement an internal class (see above) +# - classes in the net.corda.node. namespace #TODO: check that only classes in a whitelist are part of the API rather than look for specific invalid cases going forward -newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "\.internal\." ) +newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "(? + + @@ -158,6 +160,8 @@ + + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index debfe40667..436ccfa28f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -14,6 +14,7 @@ see changes to this list. * Ajitha Thayaharan (BCS Technology International) * Alberto Arri (R3) * amiracam +* Amol Pednekar * Andras Slemmer (R3) * Andrius Dagys (R3) * Andrzej Cichocki (R3) @@ -79,6 +80,7 @@ see changes to this list. * Emanuel Russo (NTT DATA Italy) * Farzad Pezeshkpour (RBS) * fracting +* Francisco Spadafora (NTT DATA Italy) * Frederic Dalibard (Natixis) * Garrett Macey (Wells Fargo) * gary-rowe @@ -212,6 +214,7 @@ see changes to this list. * verymahler * Viktor Kolomeyko (R3) * Vipin Bharathan +* Vitalij Reicherdt (Commerzbank AG) * Wawrzek Niewodniczanski (R3) * Wei Wu Zhang (Commonwealth Bank of Australia) * Zabrina Smith (Northern Trust) diff --git a/LICENSE b/LICENSE index 3e5757f3ff..e3a5c7f231 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Copyright 2016 - 2018, R3 Limited. + Copyright 2016 - 2019, R3 Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index a1232f8270..d453458f42 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,9 @@ Corda is an open source blockchain project, designed for business from the start ## Contributing -We welcome contributions to Corda! Please see our [CONTRIBUTING.md](./CONTRIBUTING.md). +Corda is an open-source project and contributions are welcome! + +To find out how to contribute, please see our [contributing docs](https://docs.corda.net/head/contributing-index.html). ## License diff --git a/build.gradle b/build.gradle index 3a20bd5e99..53d5f7953f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { file("$projectDir/constants.properties").withInputStream { constants.load(it) } // Our version: bump this on release. - ext.corda_release_version = "4.0-SNAPSHOT" + ext.corda_release_version = "5.0-SNAPSHOT" ext.corda_platform_version = constants.getProperty("platformVersion") ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") @@ -383,8 +383,6 @@ bintrayConfig { 'corda-tools-explorer', 'corda-tools-network-bootstrapper', 'corda-tools-cliutils', - 'corda-notary-raft', - 'corda-notary-bft-smart', 'corda-common-configuration-parsing', 'corda-common-validation' ] @@ -474,4 +472,4 @@ wrapper { buildScan { termsOfServiceUrl = 'https://gradle.com/terms-of-service' termsOfServiceAgree = 'yes' -} \ No newline at end of file +} 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 fbdb10c67a..7f8db6e26f 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 @@ -10,6 +10,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.module.kotlin.convertValue import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever import net.corda.client.jackson.internal.childrenAs import net.corda.client.jackson.internal.valueAs @@ -24,7 +25,8 @@ import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.services.AttachmentStorage -import net.corda.core.node.services.NetworkParametersStorage +import net.corda.core.node.services.NetworkParametersService +import net.corda.core.node.services.TransactionStorage import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.serialize @@ -94,10 +96,10 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: services = rigorousMock() cordappProvider = rigorousMock() val networkParameters = testNetworkParameters(minimumPlatformVersion = 4) - val networkParametersStorage = rigorousMock().also { + val networkParametersService = rigorousMock().also { doReturn(networkParameters.serialize().hash).whenever(it).currentHash } - doReturn(networkParametersStorage).whenever(services).networkParametersStorage + doReturn(networkParametersService).whenever(services).networkParametersService doReturn(cordappProvider).whenever(services).cordappProvider doReturn(networkParameters).whenever(services).networkParameters doReturn(attachments).whenever(services).attachments @@ -237,6 +239,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: doReturn(attachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) val attachmentStorage = rigorousMock() doReturn(attachmentStorage).whenever(services).attachments + doReturn(mock()).whenever(services).validatedTransactions val attachment = rigorousMock() doReturn(attachment).whenever(attachmentStorage).openAttachment(attachmentId) doReturn(attachmentId).whenever(attachment).id diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index 8f5c1fe4dd..3b446681d2 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -29,6 +29,7 @@ import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver import net.corda.testing.internal.chooseIdentity import net.corda.testing.node.User +import net.corda.testing.node.internal.FINANCE_CORDAPPS import org.junit.Test import rx.Observable @@ -49,7 +50,7 @@ class NodeMonitorModelTest { private lateinit var newNode: (CordaX500Name) -> NodeInfo private fun setup(runTest: () -> Unit) { - driver(DriverParameters(extraCordappPackagesToScan = listOf("net.corda.finance"))) { + driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS)) { val cashUser = User("user1", "test", permissions = setOf(all())) val aliceNodeHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(cashUser)).getOrThrow() aliceNode = aliceNodeHandle.nodeInfo diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle index f4d655d966..c30049ee08 100644 --- a/client/rpc/build.gradle +++ b/client/rpc/build.gradle @@ -57,10 +57,13 @@ processSmokeTestResources { rename 'corda-(.*)', 'corda.jar' } from(project(':finance:workflows').tasks['jar']) { - rename 'finance-workflows-(.*)', 'finance-workflows.jar' + rename '.*finance-workflows-.*', 'cordapp-finance-workflows.jar' } from(project(':finance:contracts').tasks['jar']) { - rename 'finance-contracts-(.*)', 'finance-contracts.jar' + rename '.*finance-contracts-.*', 'cordapp-finance-contracts.jar' + } + from(project(':confidential-identities').tasks['jar']) { + rename '.*confidential-identities-.*', 'cordapp-confidential-identities.jar' } } @@ -87,6 +90,7 @@ dependencies { smokeTestCompile project(':smoke-test-utils') smokeTestCompile project(':finance:contracts') smokeTestCompile project(':finance:workflows') + smokeTestCompile project(':confidential-identities') smokeTestCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" smokeTestCompile "org.apache.logging.log4j:log4j-core:$log4j_version" smokeTestCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index 12b3785082..0b1d467140 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -3,12 +3,15 @@ package net.corda.client.rpc import com.github.benmanes.caffeine.cache.Caffeine import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme +import net.corda.core.internal.loadClassesImplementing import net.corda.core.context.Actor import net.corda.core.context.Trace import net.corda.core.identity.CordaX500Name import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.messaging.ClientRpcSslOptions import net.corda.core.messaging.CordaRPCOps +import net.corda.core.serialization.SerializationCustomSerializer +import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.days @@ -19,6 +22,7 @@ import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey import net.corda.serialization.internal.amqp.SerializerFactory import java.time.Duration +import java.util.ServiceLoader /** * This class is essentially just a wrapper for an RPCConnection and can be treated identically. @@ -240,6 +244,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor( * @param configuration An optional configuration used to tweak client behaviour. * @param sslConfiguration An optional [ClientRpcSslOptions] used to enable secure communication with the server. * @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode. + * @param classLoader a classloader, which will be used (if provided) to discover available [SerializationCustomSerializer]s and [SerializationWhitelist]s * The client will attempt to connect to a live server by trying each address in the list. If the servers are not in * HA mode, the client will round-robin from the beginning of the list and try all servers. */ @@ -252,8 +257,9 @@ class CordaRPCClient private constructor( ) { @JvmOverloads constructor(hostAndPort: NetworkHostAndPort, - configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) - : this(hostAndPort, configuration, null) + configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT, + classLoader: ClassLoader? = null) + : this(hostAndPort, configuration, null, classLoader = classLoader) /** * @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode. @@ -287,7 +293,7 @@ class CordaRPCClient private constructor( sslConfiguration: ClientRpcSslOptions? = null, classLoader: ClassLoader? = null ): CordaRPCClient { - return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader) + return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader = classLoader) } } @@ -296,7 +302,14 @@ class CordaRPCClient private constructor( effectiveSerializationEnv } catch (e: IllegalStateException) { try { - AMQPClientSerializationScheme.initialiseSerialization(classLoader, Caffeine.newBuilder().maximumSize(128).build().asMap()) + // If the client has provided a classloader, the associated classpath is checked for available custom serializers and serialization whitelists. + if (classLoader != null) { + val customSerializers = loadClassesImplementing(classLoader, SerializationCustomSerializer::class.java) + val serializationWhitelists = ServiceLoader.load(SerializationWhitelist::class.java, classLoader).toSet() + AMQPClientSerializationScheme.initialiseSerialization(classLoader, customSerializers, serializationWhitelists, Caffeine.newBuilder().maximumSize(128).build().asMap()) + } else { + AMQPClientSerializationScheme.initialiseSerialization(classLoader, serializerFactoriesForContexts = Caffeine.newBuilder().maximumSize(128).build().asMap()) + } } catch (e: IllegalStateException) { // Race e.g. two of these constructed in parallel, ignore. } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt index 92a78126a6..7e1fdf9477 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/PermissionException.kt @@ -10,4 +10,4 @@ import net.corda.nodeapi.exceptions.RpcSerializableError * Thrown to indicate that the calling user does not have permission for something they have requested (for example * calling a method). */ -class PermissionException(message: String) : CordaRuntimeException(message), RpcSerializableError, ClientRelevantError \ No newline at end of file +class PermissionException(val msg: String) : CordaRuntimeException(msg), RpcSerializableError, ClientRelevantError \ No newline at end of file diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt index dbb073b8a7..f920a8081d 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt @@ -5,6 +5,7 @@ import net.corda.core.internal.toSynchronised import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext.UseCase import net.corda.core.serialization.SerializationCustomSerializer +import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.serialization.internal.* @@ -17,24 +18,25 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer */ class AMQPClientSerializationScheme( cordappCustomSerializers: Set>, + cordappSerializationWhitelists: Set, serializerFactoriesForContexts: MutableMap - ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { - constructor(cordapps: List) : this(cordapps.customSerializers, AccessOrderLinkedHashMap(128).toSynchronised()) - constructor(cordapps: List, serializerFactoriesForContexts: MutableMap) : this(cordapps.customSerializers, serializerFactoriesForContexts) + ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts) { + constructor(cordapps: List) : this(cordapps.customSerializers, cordapps.serializationWhitelists, AccessOrderLinkedHashMap(128).toSynchronised()) + constructor(cordapps: List, serializerFactoriesForContexts: MutableMap) : this(cordapps.customSerializers, cordapps.serializationWhitelists, serializerFactoriesForContexts) @Suppress("UNUSED") - constructor() : this(emptySet(), AccessOrderLinkedHashMap(128).toSynchronised()) + constructor() : this(emptySet(), emptySet(), AccessOrderLinkedHashMap(128).toSynchronised()) companion object { /** Call from main only. */ - fun initialiseSerialization(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap = AccessOrderLinkedHashMap(128).toSynchronised()) { - nodeSerializationEnv = createSerializationEnv(classLoader, serializerFactoriesForContexts) + fun initialiseSerialization(classLoader: ClassLoader? = null, customSerializers: Set> = emptySet(), serializationWhitelists: Set = emptySet(), serializerFactoriesForContexts: MutableMap = AccessOrderLinkedHashMap(128).toSynchronised()) { + nodeSerializationEnv = createSerializationEnv(classLoader, customSerializers, serializationWhitelists, serializerFactoriesForContexts) } - fun createSerializationEnv(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap = AccessOrderLinkedHashMap(128).toSynchronised()): SerializationEnvironment { + fun createSerializationEnv(classLoader: ClassLoader? = null, customSerializers: Set> = emptySet(), serializationWhitelists: Set = emptySet(), serializerFactoriesForContexts: MutableMap = AccessOrderLinkedHashMap(128).toSynchronised()): SerializationEnvironment { return SerializationEnvironment.with( SerializationFactoryImpl().apply { - registerScheme(AMQPClientSerializationScheme(emptyList(), serializerFactoriesForContexts)) + registerScheme(AMQPClientSerializationScheme(customSerializers, serializationWhitelists, serializerFactoriesForContexts)) }, storageContext = AMQP_STORAGE_CONTEXT, p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT, diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java index 32e2747072..9527a63b77 100644 --- a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java +++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java @@ -30,6 +30,27 @@ import static kotlin.test.AssertionsKt.fail; import static net.corda.finance.contracts.GetBalances.getCashBalance; public class StandaloneCordaRPCJavaClientTest { + + public static void copyCordapps(NodeProcess.Factory factory, NodeConfig notaryConfig) { + Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME)); + try { + Files.createDirectories(cordappsDir); + } catch (IOException ex) { + fail("Failed to create directories"); + } + try (Stream paths = Files.walk(Paths.get("build", "resources", "smokeTest"))) { + paths.filter(path -> path.toFile().getName().startsWith("cordapp")).forEach(file -> { + try { + Files.copy(file, cordappsDir.resolve(file.getFileName())); + } catch (IOException ex) { + fail("Failed to copy cordapp jar"); + } + }); + } catch (IOException e) { + fail("Failed to walk files"); + } + } + private List perms = Collections.singletonList("ALL"); private Set permSet = new HashSet<>(perms); private User rpcUser = new User("user1", "test", permSet); @@ -55,7 +76,7 @@ public class StandaloneCordaRPCJavaClientTest { @Before public void setUp() { factory = new NodeProcess.Factory(); - copyFinanceCordapp(); + copyCordapps(factory, notaryConfig); notary = factory.create(notaryConfig); connection = notary.connect(); rpcProxy = connection.getProxy(); @@ -73,28 +94,6 @@ public class StandaloneCordaRPCJavaClientTest { } } - private void copyFinanceCordapp() { - Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME)); - try { - Files.createDirectories(cordappsDir); - } catch (IOException ex) { - fail("Failed to create directories"); - } - try (Stream paths = Files.walk(Paths.get("build", "resources", "smokeTest"))) { - paths.forEach(file -> { - if (file.toString().contains("corda-finance")) { - try { - Files.copy(file, cordappsDir.resolve(file.getFileName())); - } catch (IOException ex) { - fail("Failed to copy finance jar"); - } - } - }); - } catch (IOException e) { - fail("Failed to walk files"); - } - } - @Test public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException { Amount dollars123 = new Amount<>(123, Currency.getInstance("USD")); diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index d72b83ea04..18884a1ad5 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -6,12 +6,15 @@ import net.corda.client.rpc.CordaRPCConnection import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.* +import net.corda.core.internal.InputStreamAndHash import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* -import net.corda.core.utilities.* +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS @@ -21,6 +24,7 @@ import net.corda.finance.contracts.getCashBalance import net.corda.finance.contracts.getCashBalances import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow +import net.corda.java.rpc.StandaloneCordaRPCJavaClientTest import net.corda.nodeapi.internal.config.User import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess @@ -31,11 +35,9 @@ import org.junit.Ignore import org.junit.Test import java.io.FilterInputStream import java.io.InputStream -import java.nio.file.Paths import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicInteger -import kotlin.streams.toList import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals @@ -69,7 +71,7 @@ class StandaloneCordaRPClientTest { @Before fun setUp() { factory = NodeProcess.Factory() - copyFinanceCordapp() + StandaloneCordaRPCJavaClientTest.copyCordapps(factory, notaryConfig) notary = factory.create(notaryConfig) connection = notary.connect() rpcProxy = connection.proxy @@ -86,15 +88,6 @@ class StandaloneCordaRPClientTest { } } - private fun copyFinanceCordapp() { - val cordappsDir = (factory.baseDirectory(notaryConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories() - // Find the finance jar file for the smoke tests of this module - val financeJars = Paths.get("build", "resources", "smokeTest").list { - it.filter { "corda-finance" in it.toString() }.toList() - } - financeJars.forEach { it.copyToDirectory(cordappsDir) } - } - @Test fun `test attachments`() { diff --git a/confidential-identities/build.gradle b/confidential-identities/build.gradle index 38a2f43807..3e38d2a870 100644 --- a/confidential-identities/build.gradle +++ b/confidential-identities/build.gradle @@ -6,12 +6,13 @@ apply plugin: 'kotlin' apply plugin: CanonicalizerPlugin apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.quasar-utils' +apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'com.jfrog.artifactory' description 'Corda Experimental Confidential Identities' dependencies { - compile project(':core') + cordaCompile project(':core') testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "junit:junit:$junit_version" @@ -26,6 +27,17 @@ dependencies { testCompile "org.assertj:assertj-core:$assertj_version" } +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion 1 + workflow { + name "Corda Experimental Confidential Identities" + versionId 1 + vendor "R3" + licence "Open Source (Apache 2)" + } +} + jar { baseName 'corda-confidential-identities' } diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt index 50f21923cc..085aa22784 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt @@ -4,29 +4,49 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.CordaInternal import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.verify -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.FlowSession +import net.corda.core.flows.* import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.cordapp.CordappResolver +import net.corda.core.internal.warnOnce import net.corda.core.node.ServiceHub import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap import java.security.PublicKey import java.security.SignatureException +import java.util.* /** * Very basic flow which generates new confidential identities for parties in a transaction and exchanges the transaction * key and certificate paths between the parties. This is intended for use as a sub-flow of another flow which builds a * transaction. The flow running on the other side must also call this flow at the correct location. + * + * NOTE: This is an inlined flow but for backwards compatibility is annotated with [InitiatingFlow]. */ -class SwapIdentitiesFlow @JvmOverloads constructor(private val otherSideSession: FlowSession, - override val progressTracker: ProgressTracker = tracker()) : FlowLogic() { +@InitiatingFlow +class SwapIdentitiesFlow +private constructor(private val otherSideSession: FlowSession?, + private val otherParty: Party?, + override val progressTracker: ProgressTracker) : FlowLogic>() { + + @JvmOverloads + constructor(otherSideSession: FlowSession, progressTracker: ProgressTracker = tracker()) : this(otherSideSession, null, progressTracker) + + @Deprecated("It is unsafe to use this constructor as it requires nodes to automatically vend anonymous identities without first " + + "checking if they should. Instead, use the constructor that takes in an existing FlowSession.") + constructor(otherParty: Party, revocationEnabled: Boolean, progressTracker: ProgressTracker) : this(null, otherParty, progressTracker) + + @Deprecated("It is unsafe to use this constructor as it requires nodes to automatically vend anonymous identities without first " + + "checking if they should. Instead, use the constructor that takes in an existing FlowSession.") + constructor(otherParty: Party) : this(null, otherParty, tracker()) + companion object { object GENERATING_IDENTITY : ProgressTracker.Step("Generating our anonymous identity") object SIGNING_IDENTITY : ProgressTracker.Step("Signing our anonymous identity") @@ -69,38 +89,49 @@ class SwapIdentitiesFlow @JvmOverloads constructor(private val otherSideSession: } @Suspendable - override fun call(): AnonymousResult { + override fun call(): LinkedHashMap { + val session = otherSideSession ?: run { + logger.warnOnce("The current usage of SwapIdentitiesFlow is unsafe. Please consider upgrading your CorDapp to use " + + "SwapIdentitiesFlow with FlowSessions. (${CordappResolver.currentCordapp?.info})") + initiateFlow(otherParty!!) + } progressTracker.currentStep = GENERATING_IDENTITY val ourAnonymousIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false) val data = buildDataToSign(ourAnonymousIdentity) progressTracker.currentStep = SIGNING_IDENTITY val signature = serviceHub.keyManagementService.sign(data, ourAnonymousIdentity.owningKey).withoutKey() - val ourIdentWithSig = IdentityWithSignature(ourAnonymousIdentity, signature) + val ourIdentWithSig = IdentityWithSignature(ourAnonymousIdentity.serialize(), signature) progressTracker.currentStep = AWAITING_IDENTITY - val theirAnonymousIdentity = otherSideSession.sendAndReceive(ourIdentWithSig).unwrap { theirIdentWithSig -> + val theirAnonymousIdentity = session.sendAndReceive(ourIdentWithSig).unwrap { theirIdentWithSig -> progressTracker.currentStep = VERIFYING_IDENTITY - validateAndRegisterIdentity(serviceHub, otherSideSession.counterparty, theirIdentWithSig.identity, theirIdentWithSig.signature) + validateAndRegisterIdentity(serviceHub, session.counterparty, theirIdentWithSig.identity.deserialize(), theirIdentWithSig.signature) } - return AnonymousResult(ourAnonymousIdentity.party.anonymise(), theirAnonymousIdentity.party.anonymise()) + + val identities = LinkedHashMap() + identities[ourIdentity] = ourAnonymousIdentity.party.anonymise() + identities[session.counterparty] = theirAnonymousIdentity.party.anonymise() + return identities } - /** - * Result class containing the caller's anonymous identity ([ourIdentity]) and the counterparty's ([theirIdentity]). - */ @CordaSerializable - data class AnonymousResult(val ourIdentity: AnonymousParty, val theirIdentity: AnonymousParty) - - @CordaSerializable - private data class IdentityWithSignature(val identity: PartyAndCertificate, val signature: DigitalSignature) - - /** - * Data class used only in the context of asserting that the owner of the private key for the listed key wants to use it - * to represent the named entity. This is paired with an X.509 certificate (which asserts the signing identity says - * the key represents the named entity) and protects against a malicious party incorrectly claiming others' - * keys. - */ - @CordaSerializable - private data class CertificateOwnershipAssertion(val name: CordaX500Name, val owningKey: PublicKey) + data class IdentityWithSignature(val identity: SerializedBytes, val signature: DigitalSignature) } +// Data class used only in the context of asserting that the owner of the private key for the listed key wants to use it +// to represent the named entity. This is paired with an X.509 certificate (which asserts the signing identity says +// the key represents the named entity) and protects against a malicious party incorrectly claiming others' +// keys. +@CordaSerializable +data class CertificateOwnershipAssertion(val x500Name: CordaX500Name, val publicKey: PublicKey) + open class SwapIdentitiesException @JvmOverloads constructor(message: String, cause: Throwable? = null) : FlowException(message, cause) + +// This only exists for backwards compatibility +@InitiatedBy(SwapIdentitiesFlow::class) +private class SwapIdentitiesHandler(private val otherSide: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + subFlow(SwapIdentitiesFlow(otherSide)) + logger.warnOnce("Insecure API to swap anonymous identities was used by ${otherSide.counterparty} (${otherSide.getCounterpartyFlowInfo()})") + } +} 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 0b87a857bc..95b0664c98 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -171,10 +171,7 @@ class SwapIdentitiesFlowTests { @InitiatingFlow private class SwapIdentitiesInitiator(private val otherSide: Party) : FlowLogic>() { @Suspendable - override fun call(): Map { - val (anonymousUs, anonymousThem) = subFlow(SwapIdentitiesFlow(initiateFlow(otherSide))) - return mapOf(ourIdentity to anonymousUs, otherSide to anonymousThem) - } + override fun call(): Map = subFlow(SwapIdentitiesFlow(initiateFlow(otherSide))) } @InitiatedBy(SwapIdentitiesInitiator::class) diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml index 17ae160b02..6e27ecccb2 100644 --- a/config/dev/log4j2.xml +++ b/config/dev/log4j2.xml @@ -10,27 +10,59 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - +