CORDA-1621: The finance CorDapp uses the app config feature rather than the node's config (#4100)

This commit is contained in:
Shams Asari 2018-10-22 18:56:30 +01:00 committed by GitHub
parent c15b693f06
commit d3c5479826
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 213 additions and 203 deletions

View File

@ -20,17 +20,15 @@ Get the testing tools
To run the tests and make sure your node is connecting correctly to the network you will need to download and install a
couple of resources.
1. Log into your Cloud VM via SSH.
#. Log into your Cloud VM via SSH.
2. Stop the Corda node(s) running on your cloud instance.
#. Stop the Corda node(s) running on your cloud instance.
.. code:: bash
ps aux | grep corda.jar | awk '{ print $2 }' | xargs sudo kill
3. Download the finance CorDapp
#. Download the finance CorDapp
In the terminal on your cloud instance run:
@ -38,32 +36,32 @@ couple of resources.
wget https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda-finance/<VERSION>-corda/corda-finance-<VERSION>-corda.jar
This is required to run some flows to check your connections, and to issue/transfer cash to counterparties. Copy it to the Corda installation location:
This is required to run some flows to check your connections, and to issue/transfer cash to counterparties. Copy it to
the Corda installation location:
.. code:: bash
sudo cp /home/<USER>/corda-finance-<VERSION>-corda.jar /opt/corda/cordapps/
4. Add the following line to the bottom of your ``node.conf``:
#. Run the following to create a config file for the finance CorDapp:
.. code:: bash
issuableCurrencies : [ USD ]
echo "issuableCurrencies : [ USD ]" > /opt/corda/cordapps/config/corda-finance-<VERSION>-corda.conf
.. note:: Make sure that the config file is in the correct format, e.g., by ensuring that there's a comma at the end of the line prior to the added config.
4. Restart the Corda node:
#. Restart the Corda node:
.. code:: bash
cd /opt/corda
sudo ./run-corda.sh
Your node is now running the Finance Cordapp.
Your node is now running the finance Cordapp.
.. note:: You can double-check that the CorDapp is loaded in the log file ``/opt/corda/logs/node-<VM-NAME>.log``. This file will list installed apps at startup. Search for ``Loaded CorDapps`` in the logs.
.. note:: You can double-check that the CorDapp is loaded in the log file ``/opt/corda/logs/node-<VM-NAME>.log``. This
file will list installed apps at startup. Search for ``Loaded CorDapps`` in the logs.
6. Now download the Node Explorer to your **LOCAL** machine:
#. Now download the Node Explorer to your **LOCAL** machine:
.. note:: Node Explorer is a JavaFX GUI which connects to the node over the RPC interface and allows you to send transactions.
@ -73,9 +71,10 @@ couple of resources.
http://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda-tools-explorer/<VERSION>-corda/corda-tools-explorer-<VERSION>-corda.jar
.. warning:: This Node Explorer is incompatible with the Corda Enterprise distribution and vice versa as they currently use different serialisation schemes (Kryo vs AMQP).
.. warning:: This Node Explorer is incompatible with the Corda Enterprise distribution and vice versa as they currently
use different serialisation schemes (Kryo vs AMQP).
7. Run the Node Explorer tool on your **LOCAL** machine.
#. Run the Node Explorer tool on your **LOCAL** machine.
.. code:: bash
@ -90,8 +89,10 @@ Connect to the node
To connect to the node you will need:
* The IP address of your node (the public IP of your cloud instance). You can find this in the instance page of your cloud console.
* The port number of the RPC interface to the node, specified in ``/opt/corda/node.conf`` in the ``rpcSettings`` section, (by default this is 10003 on Testnet).
* The username and password of the RPC interface of the node, also in the ``node.conf`` in the ``rpcUsers`` section, (by default the username is ``cordazoneservice`` on Testnet).
* The port number of the RPC interface to the node, specified in ``/opt/corda/node.conf`` in the ``rpcSettings`` section,
(by default this is 10003 on Testnet).
* The username and password of the RPC interface of the node, also in the ``node.conf`` in the ``rpcUsers`` section,
(by default the username is ``cordazoneservice`` on Testnet).
Click on ``Connect`` to log into the node.
@ -102,7 +103,8 @@ Once Explorer has logged in to your node over RPC click on the ``Network`` tab i
.. image:: resources/explorer-network.png
If your Corda node is correctly configured and connected to the Testnet then you should be able to see the identities of your node, the Testnet notary and the network map listing all the counterparties currently on the network.
If your Corda node is correctly configured and connected to the Testnet then you should be able to see the identities of
your node, the Testnet notary and the network map listing all the counterparties currently on the network.
Test issuance transaction
@ -120,8 +122,9 @@ Click ``Execute`` and the transaction will start.
.. image:: resources/explorer-cash-issue3.png
Click on the red X to close the notification window and click on ``Transactions`` tab to see the transaction in progress, or wait for a success message to be displayed:
Click on the red X to close the notification window and click on ``Transactions`` tab to see the transaction in progress,
or wait for a success message to be displayed:
.. image:: resources/explorer-transactions.png
Congratulations! You have now successfully installed a CorDapp and executed a transaction on the Corda Testnet.
Congratulations! You have now successfully installed a CorDapp and executed a transaction on the Corda Testnet.

View File

@ -21,6 +21,7 @@ class Configuration(
nodeInterface.dbPort,
password = DEFAULT_PASSWORD
),
// TODO This is not being used when it could be. The call-site is using configElements instead.
val notary: NotaryConfiguration = NotaryConfiguration(),
val cordapps: CordappConfiguration = CordappConfiguration(),
vararg configElements: ConfigurationTemplate
@ -28,9 +29,7 @@ class Configuration(
private val developerMode = true
val cordaX500Name: CordaX500Name by lazy({
CordaX500Name(name, location, country)
})
val cordaX500Name: CordaX500Name = CordaX500Name(name, location, country)
private val basicConfig = """
|myLegalName="C=$country,L=$location,O=$name"

View File

@ -1,5 +1,7 @@
package net.corda.behave.node.configuration
// TODO This is a ConfigurationTemplate but is never used as one. Therefore the private "applications" list is never used
// and thus includeFinance isn't necessary either. Something is amiss.
class CordappConfiguration(var apps: List<String> = emptyList(), val includeFinance: Boolean = false) : ConfigurationTemplate() {
private val applications = apps + if (includeFinance) {

View File

@ -7,6 +7,10 @@ class CurrencyConfiguration(private val issuableCurrencies: List<String>) : Conf
if (issuableCurrencies.isEmpty()) {
""
} else {
// TODO This is no longer correct. issuableCurrencies is a config of the finance app and belongs
// in a separate .conf file for the app (in the config sub-directory, with a filename matching the CorDapp
// jar filename). It is no longer read in from the node conf file. There seem to be pieces missing in the
// behave framework to allow one to do this.
"""
|custom : {
| issuableCurrencies : [

View File

@ -7,9 +7,9 @@ import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.flows.CashConfigDataFlow
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.internal.CashConfigDataFlow
import java.util.*
import java.util.concurrent.TimeUnit

View File

@ -1,24 +0,0 @@
package net.corda.finance.flows.test
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.finance.EUR
import net.corda.finance.USD
import net.corda.finance.flows.CashConfigDataFlow
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class CashConfigDataFlowTest {
@Test
fun `issuable currencies are read in from node config`() {
driver(DriverParameters(
extraCordappPackagesToScan = listOf("net.corda.finance.flows"),
notarySpecs = emptyList())) {
val node = startNode(customOverrides = mapOf("custom" to mapOf("issuableCurrencies" to listOf("EUR", "USD")))).getOrThrow()
val config = node.rpc.startFlow(::CashConfigDataFlow).returnValue.getOrThrow()
assertThat(config.issuableCurrencies).containsExactly(EUR, USD)
}
}
}

View File

@ -1,77 +0,0 @@
package net.corda.finance.flows
import co.paralleluniverse.fibers.Suspendable
import com.typesafe.config.ConfigFactory
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.internal.declaredField
import net.corda.core.node.AppServiceHub
import net.corda.core.node.services.CordaService
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.finance.CHF
import net.corda.finance.EUR
import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.finance.flows.ConfigHolder.Companion.supportedCurrencies
import java.io.IOException
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.OpenOption
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
// TODO Until apps have access to their own config, we'll hack things by first getting the baseDirectory, read the node.conf
// again to get our config and store it here for access by our flow
@CordaService
class ConfigHolder(services: AppServiceHub) : SingletonSerializeAsToken() {
companion object {
val supportedCurrencies = listOf(USD, GBP, CHF, EUR)
// TODO: In future releases, the Finance app should be fully decoupled from internal APIs in Core.
private operator fun Path.div(other: String): Path = resolve(other)
private operator fun String.div(other: String): Path = Paths.get(this) / other
private fun Path.inputStream(vararg options: OpenOption): InputStream = Files.newInputStream(this, *options)
private inline fun <R> Path.read(vararg options: OpenOption, block: (InputStream) -> R): R = inputStream(*options).use(block)
}
val issuableCurrencies: List<Currency>
init {
// Warning!! You are about to see a major hack!
val baseDirectory = services.declaredField<Any>("serviceHub").value
.let { it.javaClass.getMethod("getConfiguration").apply { isAccessible = true }.invoke(it) }
.let { it.javaClass.getMethod("getBaseDirectory").apply { isAccessible = true }.invoke(it) }
.let { it.javaClass.getMethod("toString").apply { isAccessible = true }.invoke(it) as String }
var issuableCurrenciesValue: List<Currency>
try {
val config = (baseDirectory / "node.conf").read { ConfigFactory.parseReader(it.reader()) }
if (config.hasPath("custom.issuableCurrencies")) {
issuableCurrenciesValue = config.getStringList("custom.issuableCurrencies").map { Currency.getInstance(it) }
require(supportedCurrencies.containsAll(issuableCurrenciesValue))
} else {
issuableCurrenciesValue = emptyList()
}
} catch (e: IOException) {
issuableCurrenciesValue = emptyList()
}
issuableCurrencies = issuableCurrenciesValue
}
}
/**
* Flow to obtain cash cordapp app configuration.
*/
@StartableByRPC
class CashConfigDataFlow : FlowLogic<CashConfiguration>() {
@Suspendable
override fun call(): CashConfiguration {
val configHolder = serviceHub.cordaService(ConfigHolder::class.java)
return CashConfiguration(configHolder.issuableCurrencies, supportedCurrencies)
}
}
@CordaSerializable
data class CashConfiguration(val issuableCurrencies: List<Currency>, val supportedCurrencies: List<Currency>)

View File

@ -0,0 +1,48 @@
package net.corda.finance.internal
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.AppServiceHub
import net.corda.core.node.services.CordaService
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.finance.CHF
import net.corda.finance.EUR
import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.finance.internal.ConfigHolder.Companion.supportedCurrencies
import java.util.*
@CordaService
class ConfigHolder(services: AppServiceHub) : SingletonSerializeAsToken() {
companion object {
val supportedCurrencies = listOf(USD, GBP, CHF, EUR)
}
val issuableCurrencies: List<Currency>
init {
val issuableCurrenciesStringList: List<String> = uncheckedCast(services.getAppContext().config.get("issuableCurrencies"))
issuableCurrencies = issuableCurrenciesStringList.map(Currency::getInstance)
(issuableCurrencies - supportedCurrencies).let {
require(it.isEmpty()) { "$it are not supported currencies" }
}
}
}
/**
* Flow to obtain cash cordapp app configuration.
*/
@StartableByRPC
class CashConfigDataFlow : FlowLogic<CashConfiguration>() {
@Suspendable
override fun call(): CashConfiguration {
val configHolder = serviceHub.cordaService(ConfigHolder::class.java)
return CashConfiguration(configHolder.issuableCurrencies, supportedCurrencies)
}
}
@CordaSerializable
data class CashConfiguration(val issuableCurrencies: List<Currency>, val supportedCurrencies: List<Currency>)

View File

@ -0,0 +1,29 @@
package net.corda.finance.internal
import net.corda.core.internal.packageName
import net.corda.core.utilities.getOrThrow
import net.corda.finance.EUR
import net.corda.finance.USD
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.cordappForPackages
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Test
class CashConfigDataFlowTest {
private val mockNet = MockNetwork(emptyList(), MockNetworkParameters(threadPerNode = true))
@After
fun cleanUp() = mockNet.stopNodes()
@Test
fun `issuable currencies read in from cordapp config`() {
val node = mockNet.createNode(MockNodeParameters(additionalCordapps = listOf(
cordappForPackages(javaClass.packageName).withConfig(mapOf("issuableCurrencies" to listOf("EUR", "USD")))
)))
val config = node.startFlow(CashConfigDataFlow()).getOrThrow()
assertThat(config.issuableCurrencies).containsExactly(EUR, USD)
}
}

View File

@ -69,12 +69,11 @@ class FlowCheckpointVersionNodeStartupCheckTest {
// Create the CorDapp jar file manually first to get hold of the directory that will contain it so that we can
// rename the filename later. The cordappDir, which acts as pointer to the jar file, does not get renamed.
val cordappDir = TestCordappDirectories.getJarDirectory(cordapp)
val cordappJar = cordappDir.list().single()
val cordappJar = cordappDir.list().single { it.toString().endsWith(".jar") }
createSuspendedFlowInBob(setOf(cordapp))
// Rename the jar file. TestCordappDirectories caches the location of the jar file but the use of the random
// UUID in the name means there's zero chance of contaminating another test.
// Rename the jar file.
cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}")
assertBobFailsToStartWithLogMessage(
@ -88,13 +87,13 @@ class FlowCheckpointVersionNodeStartupCheckTest {
fun `restart node with incompatible version of suspended flow due to different jar hash`() {
driver(parametersForRestartingNodes()) {
val originalCordapp = defaultCordapp.withName("different-jar-hash-test-${UUID.randomUUID()}")
val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single()
val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single { it.toString().endsWith(".jar") }
createSuspendedFlowInBob(setOf(originalCordapp))
// The vendor is part of the MANIFEST so changing it is sufficient to change the jar hash
val modifiedCordapp = originalCordapp.withVendor("${originalCordapp.vendor}-modified")
val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single()
val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single { it.toString().endsWith(".jar") }
modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING)
assertBobFailsToStartWithLogMessage(

View File

@ -167,7 +167,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments).tokenize()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, transactionStorage).also {

View File

@ -5,30 +5,26 @@ import com.typesafe.config.ConfigFactory
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.isDirectory
import net.corda.core.internal.noneOrSingle
import net.corda.core.utilities.contextLogger
import java.nio.file.Path
import java.nio.file.Paths
class CordappConfigFileProvider(private val configDir: Path = DEFAULT_CORDAPP_CONFIG_DIR) : CordappConfigProvider {
class CordappConfigFileProvider(cordappDirectories: List<Path>) : CordappConfigProvider {
companion object {
val DEFAULT_CORDAPP_CONFIG_DIR = Paths.get("cordapps") / "config"
const val CONFIG_EXT = ".conf"
val logger = contextLogger()
private val logger = contextLogger()
}
init {
configDir.createDirectories()
}
private val configDirectories = cordappDirectories.map { (it / "config").createDirectories() }
override fun getConfigByName(name: String): Config {
val configFile = configDir / "$name$CONFIG_EXT"
return if (configFile.exists()) {
check(!configFile.isDirectory()) { "${configFile.toAbsolutePath()} is a directory, expected a config file" }
logger.info("Found config for cordapp $name in ${configFile.toAbsolutePath()}")
// TODO There's nothing stopping the same CorDapp jar from occuring in different directories and thus causing
// conflicts. The cordappDirectories list config option should just be a single cordappDirectory
val configFile = configDirectories.map { it / "$name.conf" }.noneOrSingle { it.exists() }
return if (configFile != null) {
logger.info("Found config for cordapp $name in $configFile")
ConfigFactory.parseFile(configFile.toFile())
} else {
logger.info("No config found for cordapp $name in ${configFile.toAbsolutePath()}")
logger.info("No config found for cordapp $name in $configDirectories")
ConfigFactory.empty()
}
}

View File

@ -12,16 +12,16 @@ import java.nio.file.Paths
class CordappConfigFileProviderTests {
private companion object {
val cordappConfDir = Paths.get("build") / "tmp" / "cordapps" / "config"
val cordappDir = Paths.get("build") / "tmp" / "cordapps"
const val cordappName = "test"
val cordappConfFile = cordappConfDir / "$cordappName.conf"
val cordappConfFile = cordappDir / "config" / "$cordappName.conf"
val validConfig: Config = ConfigFactory.parseString("key=value")
val alternateValidConfig: Config = ConfigFactory.parseString("key=alternateValue")
const val invalidConfig = "Invalid"
}
private val provider = CordappConfigFileProvider(cordappConfDir)
private val provider = CordappConfigFileProvider(listOf(cordappDir))
@Test
fun `test that config can be loaded`() {

View File

@ -56,9 +56,11 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
webPort 10007
rpcUsers = [[user: "bankUser", password: "test", permissions: ["ALL"]]]
extraConfig = [
custom : [issuableCurrencies: ["USD"]],
h2Settings: [address: "localhost:10017"]
]
cordapp(project(':finance')) {
config "issuableCurrencies = [ USD ]"
}
}
node {
name "O=BigCorporation,L=New York,C=US"

View File

@ -19,12 +19,15 @@ interface TestCordapp {
/** Returns the version string, defaults to "1.0" if not specified. */
val version: String
/** Returns the vendor string, defaults to "Corda" if not specified. */
/** Returns the vendor string, defaults to "test-vendor" if not specified. */
val vendor: String
/** Returns the target platform version, defaults to the current platform version if not specified. */
val targetVersion: Int
/** Returns the config for this CorDapp, defaults to empty if not specified. */
val config: Map<String, Any>
/** Returns the set of package names scanned for this test CorDapp. */
val packages: Set<String>
@ -43,6 +46,9 @@ interface TestCordapp {
/** Return a copy of this [TestCordapp] but with the specified target platform version. */
fun withTargetVersion(targetVersion: Int): TestCordapp
/** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */
fun withConfig(config: Map<String, Any>): TestCordapp
class Factory {
companion object {
@JvmStatic
@ -57,9 +63,10 @@ interface TestCordapp {
return TestCordappImpl(
name = "test-cordapp",
version = "1.0",
vendor = "Corda",
vendor = "test-vendor",
title = "test-title",
targetVersion = PLATFORM_VERSION,
config = emptyMap(),
packages = simplifyScanPackages(packageNames),
classes = emptySet()
)

View File

@ -1,9 +1,11 @@
package net.corda.testing.node.internal
import com.typesafe.config.ConfigValueFactory
import net.corda.core.crypto.sha256
import net.corda.core.internal.createDirectories
import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div
import net.corda.core.internal.writeText
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.testing.node.TestCordapp
@ -22,24 +24,28 @@ object TestCordappDirectories {
fun getJarDirectory(cordapp: TestCordapp, cordappsDirectory: Path = defaultCordappsDirectory): Path {
cordapp as TestCordappImpl
return testCordappsCache.computeIfAbsent(cordapp) {
val cordappDir = (cordappsDirectory / UUID.randomUUID().toString()).createDirectories()
val uniqueScanString = if (cordapp.packages.size == 1 && cordapp.classes.isEmpty()) {
cordapp.packages.first()
} else {
"${cordapp.packages}${cordapp.classes.joinToString { it.name }}".toByteArray().sha256().toString()
val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render()
val filename = cordapp.run {
val uniqueScanString = if (packages.size == 1 && classes.isEmpty() && config.isEmpty()) {
packages.first()
} else {
"$packages$classes$configString".toByteArray().sha256().toString()
}
"${name}_${vendor}_${title}_${version}_${targetVersion}_$uniqueScanString".replace(whitespace, "-")
}
val jarFileName = cordapp.run { "${name}_${vendor}_${title}_${version}_${targetVersion}_$uniqueScanString.jar".replace(whitespace, "-") }
val jarFile = cordappDir / jarFileName
val cordappDir = cordappsDirectory / UUID.randomUUID().toString()
val configDir = (cordappDir / "config").createDirectories()
val jarFile = cordappDir / "$filename.jar"
cordapp.packageAsJar(jarFile)
(configDir / "$filename.conf").writeText(configString)
logger.debug { "$cordapp packaged into $jarFile" }
cordappDir
}
}
private val defaultCordappsDirectory: Path by lazy {
val cordappsDirectory = (Paths.get("build") / "tmp" / getTimestampAsDirectoryName() / "generated-test-cordapps").toAbsolutePath()
val cordappsDirectory = Paths.get("build").toAbsolutePath() / "generated-test-cordapps" / getTimestampAsDirectoryName()
logger.info("Initialising generated test CorDapps directory in $cordappsDirectory")
cordappsDirectory.toFile().deleteOnExit()
cordappsDirectory.deleteRecursively()
cordappsDirectory.createDirectories()
}

View File

@ -7,6 +7,7 @@ data class TestCordappImpl(override val name: String,
override val vendor: String,
override val title: String,
override val targetVersion: Int,
override val config: Map<String, Any>,
override val packages: Set<String>,
val classes: Set<Class<*>>) : TestCordapp {
@ -20,6 +21,8 @@ data class TestCordappImpl(override val name: String,
override fun withTargetVersion(targetVersion: Int): TestCordappImpl = copy(targetVersion = targetVersion)
override fun withConfig(config: Map<String, Any>): TestCordappImpl = copy(config = config)
fun withClasses(vararg classes: Class<*>): TestCordappImpl {
return copy(classes = classes.filter { clazz -> packages.none { clazz.name.startsWith("$it.") } }.toSet())
}

View File

@ -85,7 +85,7 @@ class Explorer internal constructor(private val explorerController: ExplorerCont
// Note: does not copy dependencies because we should soon be making all apps fat jars and dependencies implicit.
//
// TODO: Remove this code when serialisation has been upgraded.
val cordappsDir = config.explorerDir / NodeConfig.cordappDirName
val cordappsDir = config.explorerDir / NodeConfig.CORDAPP_DIR_NAME
cordappsDir.createDirectories()
config.cordappsDir.list {
it.forEachOrdered { path ->

View File

@ -3,6 +3,7 @@ package net.corda.demobench.model
import com.typesafe.config.*
import com.typesafe.config.ConfigFactory.empty
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.copyToDirectory
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
@ -11,7 +12,7 @@ import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.toConfig
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.util.Properties
import java.util.*
/**
* This is a subset of FullNodeConfiguration, containing only those configs which we need. The node uses reference.conf
@ -38,33 +39,33 @@ data class NodeConfig(
companion object {
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
val defaultUser = user("guest")
const val cordappDirName = "cordapps"
const val CORDAPP_DIR_NAME = "cordapps"
}
fun nodeConf(): Config {
@VisibleForTesting
internal fun nodeConf(): Config {
val rpcSettings: ConfigObject = empty()
.withValue("address", valueFor(rpcSettings.address.toString()))
.withValue("adminAddress", valueFor(rpcSettings.adminAddress.toString()))
.root()
val customMap: Map<String, Any> = HashMap<String, Any>().also {
if (issuableCurrencies.isNotEmpty()) {
it["issuableCurrencies"] = issuableCurrencies
}
}
val custom: ConfigObject = ConfigFactory.parseMap(customMap).root()
return NodeConfigurationData(myLegalName, p2pAddress, this.rpcSettings.address, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode)
.toConfig()
.withoutPath("rpcAddress")
.withoutPath("rpcAdminAddress")
.withValue("rpcSettings", rpcSettings)
.withOptionalValue("custom", custom)
}
fun webServerConf() = WebServerConfigurationData(myLegalName, rpcSettings.address, webAddress, rpcUsers).asConfig()
@VisibleForTesting
internal fun webServerConf() = WebServerConfigurationData(myLegalName, rpcSettings.address, webAddress, rpcUsers).asConfig()
fun toNodeConfText() = nodeConf().render()
fun toNodeConfText(): String = nodeConf().render()
fun toWebServerConfText() = webServerConf().render()
fun toWebServerConfText(): String = webServerConf().render()
@VisibleForTesting
internal fun financeConf() = FinanceConfData(issuableCurrencies).toConfig()
fun toFinanceConfText(): String = financeConf().render()
fun serialiseAsString(): String = toConfig().render()
@ -92,6 +93,8 @@ private data class WebServerConfigurationData(
fun asConfig() = toConfig()
}
private data class FinanceConfData(val issuableCurrencies: List<String>)
/**
* This is a subset of NotaryConfig. It implements [ExtraService] to avoid unnecessary copying.
*/
@ -104,7 +107,7 @@ data class NodeConfigWrapper(val baseDir: Path, val nodeConfig: NodeConfig) : Ha
val key: String = nodeConfig.myLegalName.organisation.toKey()
val nodeDir: Path = baseDir / key
val explorerDir: Path = baseDir / "$key-explorer"
override val cordappsDir: Path = nodeDir / NodeConfig.cordappDirName
override val cordappsDir: Path = nodeDir / NodeConfig.CORDAPP_DIR_NAME
var state: NodeState = NodeState.STARTING
fun install(cordapps: Collection<Path>) {

View File

@ -112,18 +112,14 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
try {
// Notary can be removed and then added again, that's why we need to perform this check.
require((config.nodeConfig.notary != null).xor(notaryIdentity != null)) { "There must be exactly one notary in the network" }
config.nodeDir.createDirectories()
val cordappConfigDir = (config.cordappsDir / "config").createDirectories()
// Install any built-in plugins into the working directory.
cordappController.populate(config)
// Write this node's configuration file into its working directory.
val confFile = config.nodeDir / "node.conf"
confFile.writeText(config.nodeConfig.toNodeConfText())
// Write this node's configuration file into its working directory.
val webConfFile = config.nodeDir / "web-server.conf"
webConfFile.writeText(config.nodeConfig.toWebServerConfText())
(config.nodeDir / "node.conf").writeText(config.nodeConfig.toNodeConfText())
(config.nodeDir / "web-server.conf").writeText(config.nodeConfig.toWebServerConfText())
(cordappConfigDir / "${CordappController.FINANCE_CORDAPP_FILENAME}.conf").writeText(config.nodeConfig.toFinanceConfText())
// Execute the Corda node
val cordaEnv = System.getenv().toMutableMap().apply {

View File

@ -12,10 +12,13 @@ import java.nio.file.StandardCopyOption
import kotlin.streams.toList
class CordappController : Controller() {
companion object {
const val FINANCE_CORDAPP_FILENAME = "corda-finance"
}
private val jvm by inject<JVMConfig>()
private val cordappDir: Path = jvm.applicationDir.resolve(NodeConfig.cordappDirName)
private val finance: Path = cordappDir.resolve("corda-finance.jar")
private val cordappDir: Path = jvm.applicationDir / NodeConfig.CORDAPP_DIR_NAME
private val financeCordappJar: Path = cordappDir / "$FINANCE_CORDAPP_FILENAME.jar"
/**
* Install any built-in cordapps that this node requires.
@ -25,8 +28,8 @@ class CordappController : Controller() {
if (!config.cordappsDir.exists()) {
config.cordappsDir.createDirectories()
}
if (finance.exists()) {
finance.copyToDirectory(config.cordappsDir, StandardCopyOption.REPLACE_EXISTING)
if (financeCordappJar.exists()) {
financeCordappJar.copyToDirectory(config.cordappsDir, StandardCopyOption.REPLACE_EXISTING)
log.info("Installed 'Finance' cordapp")
}
}
@ -39,7 +42,7 @@ class CordappController : Controller() {
if (!config.cordappsDir.isDirectory()) return emptyList()
return config.cordappsDir.walk(1) { paths ->
paths.filter(Path::isCordapp)
.filter { !finance.endsWith(it.fileName) }
.filter { !financeCordappJar.endsWith(it.fileName) }
.toList()
}
}

View File

@ -60,7 +60,7 @@ class ProfileController : Controller() {
log.info("Wrote: $file")
// Write all of the non-built-in cordapps.
val cordappDir = (nodeDir / NodeConfig.cordappDirName).createDirectory()
val cordappDir = (nodeDir / NodeConfig.CORDAPP_DIR_NAME).createDirectory()
cordappController.useCordappsFor(config).forEach {
val cordapp = it.copyToDirectory(cordappDir)
log.info("Wrote: $cordapp")

View File

@ -65,12 +65,7 @@ class NodeConfigTest {
issuableCurrencies = listOf("GBP")
)
val nodeConfig = config.nodeConf()
.withValue("baseDirectory", valueFor(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("reference.conf"))
.resolve()
val custom = nodeConfig.getConfig("custom")
assertEquals(listOf("GBP"), custom.getAnyRefList("issuableCurrencies"))
assertEquals(listOf("GBP"), config.financeConf().getStringList("issuableCurrencies"))
}
@Test

View File

@ -18,14 +18,19 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.*
import net.corda.finance.flows.AbstractCashFlow
import net.corda.finance.flows.CashExitFlow
import net.corda.finance.flows.CashExitFlow.ExitRequest
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.internal.CashConfigDataFlow
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.driver.*
import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPP
import java.time.Instant
import java.util.*
@ -63,16 +68,27 @@ class ExplorerSimulation(private val options: OptionSet) {
private fun startDemoNodes() {
val portAllocation = PortAllocation.Incremental(20000)
driver(DriverParameters(portAllocation = portAllocation, extraCordappPackagesToScan = listOf("net.corda.finance"), waitForAllNodesToFinish = true, jmxPolicy = JmxPolicy(true))) {
driver(DriverParameters(
portAllocation = portAllocation,
cordappsForAllNodes = listOf(FINANCE_CORDAPP),
waitForAllNodesToFinish = true,
jmxPolicy = JmxPolicy(true)
)) {
// TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo.
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user))
val bob = startNode(providedName = BOB_NAME, rpcUsers = listOf(user))
val ukBankName = CordaX500Name(organisation = "UK Bank Plc", locality = "London", country = "GB")
val usaBankName = CordaX500Name(organisation = "USA Bank Corp", locality = "New York", country = "US")
val issuerGBP = startNode(providedName = ukBankName, rpcUsers = listOf(manager),
customOverrides = mapOf("custom" to mapOf("issuableCurrencies" to listOf("GBP"))))
val issuerUSD = startNode(providedName = usaBankName, rpcUsers = listOf(manager),
customOverrides = mapOf("custom" to mapOf("issuableCurrencies" to listOf("USD"))))
val issuerGBP = startNode(
providedName = ukBankName,
rpcUsers = listOf(manager),
additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("GBP"))))
)
val issuerUSD = startNode(
providedName = usaBankName,
rpcUsers = listOf(manager),
additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("USD"))))
)
notaryNode = defaultNotaryNode.get()
aliceNode = alice.get()

View File

@ -7,7 +7,7 @@ import net.corda.client.jfx.utils.ChosenList
import net.corda.client.jfx.utils.map
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.finance.flows.CashConfigDataFlow
import net.corda.finance.internal.CashConfigDataFlow
import tornadofx.*
import java.util.*