mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
CORDA-1621: The finance CorDapp uses the app config feature rather than the node's config (#4100)
This commit is contained in:
parent
c15b693f06
commit
d3c5479826
@ -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.
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
@ -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 : [
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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>)
|
@ -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>)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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`() {
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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 ->
|
||||
|
@ -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>) {
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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.*
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user