OS-ENT merge

This commit is contained in:
bpaunescu 2018-07-16 10:00:16 +01:00
commit 33ce848e0c
28 changed files with 248 additions and 214 deletions

0
CONTRIBUTORS.md Normal file
View File

View File

@ -0,0 +1,56 @@
Building Corda
==============
These instructions are for downloading and building the Corda code locally. If you only wish to develop CorDapps for
use on Corda, you don't need to do this, follow the instructions at :doc:`getting-set-up` and use the precompiled binaries.
Windows
-------
Java
~~~~
1. Visit http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
2. Scroll down to "Java SE Development Kit 8uXXX" (where "XXX" is the latest minor version number)
3. Toggle "Accept License Agreement"
4. Click the download link for jdk-8uXXX-windows-x64.exe (where "XXX" is the latest minor version number)
5. Download and run the executable to install Java (use the default settings)
6. Add Java to the PATH environment variable by following the instructions at https://docs.oracle.com/javase/7/docs/webnotes/install/windows/jdk-installation-windows.html#path
7. Open a new command prompt and run ``java -version`` to test that Java is installed correctly
Git
~~~
1. Visit https://git-scm.com/download/win
2. Click the "64-bit Git for Windows Setup" download link.
3. Download and run the executable to install Git (use the default installation values) and make a note of the installation directory.
4. Open a new command prompt and type ``git --version`` to test that Git is installed correctly
Buillding Corda
~~~~~~~~~~~~~~~
1. Open a command prompt
2. Run ``git clone https://github.com/corda/corda.git``
3. Run ``gradlew build``
Debian/Ubuntu Linux
-------------------
These instructions were tested on Ubuntu Server 18.04 LTS. This distribution includes ``git`` and ``python`` so only the following steps are required:
Java
~~~~
1. Run ``sudo add-apt-repository ppa:webupd8team/java`` from the terminal. Press ENTER when prompted.
2. Run ``sudo apt-get update``
3. Then run ``sudo apt-get install oracle-java8-installer``. Press Y when prompted and agree to the licence terms.
4. Run ``java --version`` to verify that java is installed correctly
Building Corda
~~~~~~~~~~~~~~
1. Open the terminal
2. Run ``git clone https://github.com/corda/corda.git``
3. Run ``./gradlew build``

View File

@ -48,10 +48,14 @@ fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>): Amount<Is
} }
/** Sums the asset states in the list, returning null if there are none. */ /** Sums the asset states in the list, returning null if there are none. */
fun <T : Any> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrNull() fun <T : Any> Iterable<ContractState>.sumFungibleOrNull(): Amount<Issued<T>>? {
return filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrNull()
}
/** Sums the asset states in the list, returning zero of the given token if there are none. */ /** Sums the asset states in the list, returning zero of the given token if there are none. */
fun <T : Any> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrZero(token) fun <T : Any> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>): Amount<Issued<T>> {
return filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrZero(token)
}
/** /**
* Sums the obligation states in the list, throwing an exception if there are none. All state objects in the * Sums the obligation states in the list, throwing an exception if there are none. All state objects in the

View File

@ -32,6 +32,7 @@ import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.temporal.Temporal import java.time.temporal.Temporal
import java.util.* import java.util.*
import javax.security.auth.x500.X500Principal
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.reflect.KType import kotlin.reflect.KType
@ -138,6 +139,7 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set
Path::class -> Paths.get(getString(path)) Path::class -> Paths.get(getString(path))
URL::class -> URL(getString(path)) URL::class -> URL(getString(path))
UUID::class -> UUID.fromString(getString(path)) UUID::class -> UUID.fromString(getString(path))
X500Principal::class -> X500Principal(getString(path))
CordaX500Name::class -> { CordaX500Name::class -> {
when (getValue(path).valueType()) { when (getValue(path).valueType()) {
ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys)
@ -183,6 +185,7 @@ private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys:
NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse) NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse)
Path::class -> getStringList(path).map { Paths.get(it) } Path::class -> getStringList(path).map { Paths.get(it) }
URL::class -> getStringList(path).map(::URL) URL::class -> getStringList(path).map(::URL)
X500Principal::class -> getStringList(path).map(::X500Principal)
UUID::class -> getStringList(path).map { UUID.fromString(it) } UUID::class -> getStringList(path).map { UUID.fromString(it) }
CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse) CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse)
Properties::class -> getConfigList(path).map(Config::toProperties) Properties::class -> getConfigList(path).map(Config::toProperties)
@ -236,7 +239,7 @@ private fun Any.toConfigMap(): Map<String, Any> {
val configValue = if (value is String || value is Boolean || value is Number) { val configValue = if (value is String || value is Boolean || value is Number) {
// These types are supported by Config as use as is // These types are supported by Config as use as is
value value
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID) { } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID || value is X500Principal) {
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
value.toString() value.toString()
} else if (value is Enum<*>) { } else if (value is Enum<*>) {
@ -271,6 +274,7 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
NetworkHostAndPort::class.java -> map(Any?::toString) NetworkHostAndPort::class.java -> map(Any?::toString)
Path::class.java -> map(Any?::toString) Path::class.java -> map(Any?::toString)
URL::class.java -> map(Any?::toString) URL::class.java -> map(Any?::toString)
X500Principal::class.java -> map(Any?::toString)
UUID::class.java -> map(Any?::toString) UUID::class.java -> map(Any?::toString)
CordaX500Name::class.java -> map(Any?::toString) CordaX500Name::class.java -> map(Any?::toString)
Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() } Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }

View File

@ -40,7 +40,7 @@ fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStor
keyStoreFilePath.read { keyStore.load(it, pass) } keyStoreFilePath.read { keyStore.load(it, pass) }
} else { } else {
keyStore.load(null, pass) keyStore.load(null, pass)
keyStoreFilePath.parent.createDirectories() keyStoreFilePath.toAbsolutePath().parent?.createDirectories()
keyStoreFilePath.write { keyStore.store(it, pass) } keyStoreFilePath.write { keyStore.store(it, pass) }
} }
return keyStore return keyStore

View File

@ -94,6 +94,8 @@ class CordaPersistence(
// Check not in read-only mode. // Check not in read-only mode.
transaction { transaction {
check(!connection.metaData.isReadOnly) { "Database should not be readonly." } check(!connection.metaData.isReadOnly) { "Database should not be readonly." }
checkCorrectAttachmentsContractsTableName(connection)
} }
} }
@ -298,4 +300,19 @@ private fun Throwable.hasSQLExceptionCause(): Boolean =
else -> cause?.hasSQLExceptionCause() ?: false else -> cause?.hasSQLExceptionCause() ?: false
} }
class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception() class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception()
class IncompatibleAttachmentsContractsTableName(override val message: String?, override val cause: Throwable? = null) : Exception()
private fun checkCorrectAttachmentsContractsTableName(connection: Connection) {
val correctName = "NODE_ATTACHMENTS_CONTRACTS"
val incorrectV30Name = "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME"
val incorrectV31Name = "NODE_ATTCHMENTS_CONTRACTS"
fun warning(incorrectName: String, version: String) = "The database contains the older table name $incorrectName instead of $correctName, see upgrade notes to migrate from Corda database version $version https://docs.corda.net/head/upgrade-notes.html."
if (!connection.metaData.getTables(null, null, correctName, null).next()) {
if (connection.metaData.getTables(null, null, incorrectV30Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV30Name, "3.0")) }
if (connection.metaData.getTables(null, null, incorrectV31Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV31Name, "3.1")) }
}
}

View File

@ -11,6 +11,7 @@
package net.corda.nodeapi.internal.protonwrapper.netty package net.corda.nodeapi.internal.protonwrapper.netty
import io.netty.handler.ssl.SslHandler import io.netty.handler.ssl.SslHandler
import net.corda.core.crypto.newSecureRandom
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.toHex import net.corda.core.utilities.toHex
@ -117,7 +118,7 @@ internal fun createClientSslHelper(target: NetworkHostAndPort,
val sslContext = SSLContext.getInstance("TLS") val sslContext = SSLContext.getInstance("TLS")
val keyManagers = keyManagerFactory.keyManagers val keyManagers = keyManagerFactory.keyManagers
val trustManagers = trustManagerFactory.trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray() val trustManagers = trustManagerFactory.trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray()
sslContext.init(keyManagers, trustManagers, SecureRandom()) sslContext.init(keyManagers, trustManagers, newSecureRandom())
val sslEngine = sslContext.createSSLEngine(target.host, target.port) val sslEngine = sslContext.createSSLEngine(target.host, target.port)
sslEngine.useClientMode = true sslEngine.useClientMode = true
sslEngine.enabledProtocols = ArtemisTcpTransport.TLS_VERSIONS.toTypedArray() sslEngine.enabledProtocols = ArtemisTcpTransport.TLS_VERSIONS.toTypedArray()
@ -131,7 +132,7 @@ internal fun createServerSslHelper(keyManagerFactory: KeyManagerFactory,
val sslContext = SSLContext.getInstance("TLS") val sslContext = SSLContext.getInstance("TLS")
val keyManagers = keyManagerFactory.keyManagers val keyManagers = keyManagerFactory.keyManagers
val trustManagers = trustManagerFactory.trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray() val trustManagers = trustManagerFactory.trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray()
sslContext.init(keyManagers, trustManagers, SecureRandom()) sslContext.init(keyManagers, trustManagers, newSecureRandom())
val sslEngine = sslContext.createSSLEngine() val sslEngine = sslContext.createSSLEngine()
sslEngine.useClientMode = false sslEngine.useClientMode = false
sslEngine.needClientAuth = true sslEngine.needClientAuth = true

View File

@ -24,6 +24,7 @@ import java.nio.file.Path
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.util.* import java.util.*
import javax.security.auth.x500.X500Principal
import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.primaryConstructor
class ConfigParsingTest { class ConfigParsingTest {
@ -94,6 +95,11 @@ class ConfigParsingTest {
testPropertyType<URLData, URLListData, URL>(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true) testPropertyType<URLData, URLListData, URL>(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true)
} }
@Test
fun X500Principal() {
testPropertyType<X500PrincipalData, X500PrincipalListData, X500Principal>(X500Principal("C=US, L=New York, CN=Corda Root CA, OU=Corda, O=R3 HoldCo LLC"), X500Principal("O=Bank A,L=London,C=GB"), valuesToString = true)
}
@Test @Test
fun UUID() { fun UUID() {
testPropertyType<UUIDData, UUIDListData, UUID>(UUID.randomUUID(), UUID.randomUUID(), valuesToString = true) testPropertyType<UUIDData, UUIDListData, UUID>(UUID.randomUUID(), UUID.randomUUID(), valuesToString = true)
@ -334,6 +340,8 @@ class ConfigParsingTest {
data class PathListData(override val values: List<Path>) : ListData<Path> data class PathListData(override val values: List<Path>) : ListData<Path>
data class URLData(override val value: URL) : SingleData<URL> data class URLData(override val value: URL) : SingleData<URL>
data class URLListData(override val values: List<URL>) : ListData<URL> data class URLListData(override val values: List<URL>) : ListData<URL>
data class X500PrincipalData(override val value: X500Principal) : SingleData<X500Principal>
data class X500PrincipalListData(override val values: List<X500Principal>) : ListData<X500Principal>
data class UUIDData(override val value: UUID) : SingleData<UUID> data class UUIDData(override val value: UUID) : SingleData<UUID>
data class UUIDListData(override val values: List<UUID>) : ListData<UUID> data class UUIDListData(override val values: List<UUID>) : ListData<UUID>
data class CordaX500NameData(override val value: CordaX500Name) : SingleData<CordaX500Name> data class CordaX500NameData(override val value: CordaX500Name) : SingleData<CordaX500Name>

View File

@ -13,6 +13,7 @@ package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.newSecureRandom
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
@ -248,7 +249,7 @@ class X509UtilitiesTest {
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustMgrFactory.init(trustStore) trustMgrFactory.init(trustStore)
val trustManagers = trustMgrFactory.trustManagers val trustManagers = trustMgrFactory.trustManagers
context.init(keyManagers, trustManagers, SecureRandom()) context.init(keyManagers, trustManagers, newSecureRandom())
val serverSocketFactory = context.serverSocketFactory val serverSocketFactory = context.serverSocketFactory
val clientSocketFactory = context.socketFactory val clientSocketFactory = context.socketFactory

View File

@ -14,6 +14,7 @@ import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import io.netty.channel.EventLoopGroup import io.netty.channel.EventLoopGroup
import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.nio.NioEventLoopGroup
import net.corda.core.crypto.newSecureRandom
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.toFuture import net.corda.core.toFuture
@ -157,7 +158,7 @@ class ProtonWrapperTests {
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustMgrFactory.init(trustStore) trustMgrFactory.init(trustStore)
val trustManagers = trustMgrFactory.trustManagers val trustManagers = trustMgrFactory.trustManagers
context.init(keyManagers, trustManagers, SecureRandom()) context.init(keyManagers, trustManagers, newSecureRandom())
val serverSocketFactory = context.serverSocketFactory val serverSocketFactory = context.serverSocketFactory

View File

@ -0,0 +1,66 @@
package net.corda.node.persistence
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.internal.packageName
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions
import net.corda.testMessage.Message
import net.corda.testMessage.MessageState
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.RandomFree
import net.corda.testing.node.User
import org.junit.Test
import java.nio.file.Path
import java.sql.DriverManager
import kotlin.test.*
class FailNodeOnNotMigratedAttachmentContractsTableNameTests {
@Test
fun `node fails when detecting table name not migrated from version 3 dot 0`() {
`node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME")
}
@Test
fun `node fails when detecting table name not migrated from version 3 dot 1`() {
`node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTCHMENTS_CONTRACTS")
}
fun `node fails when not detecting compatible table name`(tableNameFromMapping: String, tableNameInDB: String) {
val user = User("mark", "dadada", setOf(Permissions.startFlow<SendMessageFlow>(), Permissions.invokeRpc("vaultQuery")))
val message = Message("Hello world!")
val baseDir: Path = driver(DriverParameters(inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(),
portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
val (nodeName, baseDir) = {
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeName = nodeHandle.nodeInfo.singleIdentity().name
CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow()
}
nodeHandle.stop()
Pair(nodeName, nodeHandle.baseDirectory)
}()
// replace the correct table name with one from the former release
DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use {
it.createStatement().execute("ALTER TABLE $tableNameFromMapping RENAME TO $tableNameInDB")
it.commit()
}
assertFailsWith(net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName::class) {
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow()
nodeHandle.stop()
}
baseDir
}
// check that the node didn't recreated the correct table matching it's entity mapping
val (hasTableFromMapping, hasTableFromDB) = DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use {
Pair(it.metaData.getTables(null, null, tableNameFromMapping, null).next(),
it.metaData.getTables(null, null, tableNameInDB, null).next())
}
assertFalse(hasTableFromMapping)
assertTrue(hasTableFromDB)
}
}

View File

@ -150,12 +150,7 @@ import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.*
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.nodeapi.internal.persistence.isH2Database
import net.corda.nodeapi.internal.storeLegalIdentity import net.corda.nodeapi.internal.storeLegalIdentity
import net.corda.tools.shell.InteractiveShell import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
@ -1179,7 +1174,8 @@ fun configureDatabase(hikariProperties: Properties,
when { when {
ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException("Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.", ex) ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException("Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.", ex)
ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException("Could not find the database driver class. Please add it to the 'drivers' folder. See: https://docs.corda.net/corda-configuration-file.html") ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException("Could not find the database driver class. Please add it to the 'drivers' folder. See: https://docs.corda.net/corda-configuration-file.html")
else -> throw ex ex is IncompatibleAttachmentsContractsTableName -> throw ex
else -> throw CouldNotCreateDataSourceException("Could not create the DataSource: ${ex.message}", ex)
} }
} }
} }

View File

@ -203,7 +203,7 @@ open class Node(configuration: NodeConfiguration,
if (!configuration.messagingServerExternal) { if (!configuration.messagingServerExternal) {
val brokerBindAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("0.0.0.0", configuration.p2pAddress.port) val brokerBindAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("0.0.0.0", configuration.p2pAddress.port)
messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize, info.legalIdentities.map { it.owningKey }) messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize)
} }
val serverAddress = configuration.messagingServerAddress val serverAddress = configuration.messagingServerAddress

View File

@ -45,6 +45,7 @@ import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
import net.corda.nodeapi.internal.persistence.DatabaseMigrationException import net.corda.nodeapi.internal.persistence.DatabaseMigrationException
import net.corda.nodeapi.internal.persistence.oracleJdbcDriverSerialFilter import net.corda.nodeapi.internal.persistence.oracleJdbcDriverSerialFilter
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName
import net.corda.tools.shell.InteractiveShell import net.corda.tools.shell.InteractiveShell
import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole import org.fusesource.jansi.AnsiConsole
@ -178,6 +179,10 @@ open class NodeStartup(val args: Array<String>) {
} catch (e: NetworkParametersReader.Error) { } catch (e: NetworkParametersReader.Error) {
logger.error(e.message) logger.error(e.message)
return false return false
} catch (e: IncompatibleAttachmentsContractsTableName) {
e.message?.let { Node.printWarning(it) }
logger.error(e.message)
return false
} catch (e: Exception) { } catch (e: Exception) {
if (e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) { if (e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) {
logger.error("One of the ports required by the Corda node is already in use.") logger.error("One of the ports required by the Corda node is already in use.")

View File

@ -33,6 +33,7 @@ import java.net.URL
import java.nio.file.Path import java.nio.file.Path
import java.time.Duration import java.time.Duration
import java.util.* import java.util.*
import javax.security.auth.x500.X500Principal
val Int.MB: Long get() = this * 1024L * 1024L val Int.MB: Long get() = this * 1024L * 1024L
@ -78,7 +79,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5)
val extraNetworkMapKeys: List<UUID> val extraNetworkMapKeys: List<UUID>
val tlsCertCrlDistPoint: URL? val tlsCertCrlDistPoint: URL?
val tlsCertCrlIssuer: String? val tlsCertCrlIssuer: X500Principal?
val effectiveH2Settings: NodeH2Settings? val effectiveH2Settings: NodeH2Settings?
val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS
val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
@ -222,7 +223,7 @@ data class NodeConfigurationImpl(
override val compatibilityZoneURL: URL? = null, override val compatibilityZoneURL: URL? = null,
override var networkServices: NetworkServicesConfig? = null, override var networkServices: NetworkServicesConfig? = null,
override val tlsCertCrlDistPoint: URL? = null, override val tlsCertCrlDistPoint: URL? = null,
override val tlsCertCrlIssuer: String? = null, override val tlsCertCrlIssuer: X500Principal? = null,
override val rpcUsers: List<User>, override val rpcUsers: List<User>,
override val security: SecurityConfiguration? = null, override val security: SecurityConfiguration? = null,
override val verifierType: VerifierType, override val verifierType: VerifierType,
@ -296,11 +297,6 @@ data class NodeConfigurationImpl(
if (tlsCertCrlDistPoint == null) { if (tlsCertCrlDistPoint == null) {
errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL" errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL"
} }
try {
X500Name(tlsCertCrlIssuer)
} catch (e: Exception) {
errors += "Error when parsing tlsCertCrlIssuer: ${e.message}"
}
} }
if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) { if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) {
errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE" errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE"

View File

@ -25,18 +25,14 @@ import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor
import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
import net.corda.nodeapi.internal.requireOnDefaultFileSystem import net.corda.nodeapi.internal.requireOnDefaultFileSystem
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
import org.apache.activemq.artemis.core.config.Configuration import org.apache.activemq.artemis.core.config.Configuration
import org.apache.activemq.artemis.core.config.CoreAddressConfiguration
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
import org.apache.activemq.artemis.core.security.Role import org.apache.activemq.artemis.core.security.Role
@ -45,7 +41,6 @@ import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
import java.io.IOException import java.io.IOException
import java.security.KeyStoreException import java.security.KeyStoreException
import java.security.PublicKey
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
import javax.security.auth.login.AppConfigurationEntry import javax.security.auth.login.AppConfigurationEntry
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.REQUIRED import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.REQUIRED
@ -66,8 +61,7 @@ import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.RE
@ThreadSafe @ThreadSafe
class ArtemisMessagingServer(private val config: NodeConfiguration, class ArtemisMessagingServer(private val config: NodeConfiguration,
private val messagingServerAddress: NetworkHostAndPort, private val messagingServerAddress: NetworkHostAndPort,
private val maxMessageSize: Int, private val maxMessageSize: Int) : ArtemisBroker, SingletonSerializeAsToken() {
private val identities: List<PublicKey> = emptyList()) : ArtemisBroker, SingletonSerializeAsToken() {
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
} }
@ -131,48 +125,29 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
log.info("P2P messaging server listening on $messagingServerAddress") log.info("P2P messaging server listening on $messagingServerAddress")
} }
private fun createArtemisConfig(): Configuration { private fun createArtemisConfig() = SecureArtemisConfiguration().apply {
val addressConfigs = identities.map { val artemisDir = config.baseDirectory / "artemis"
val queueName = ArtemisMessagingComponent.RemoteInboxAddress(it).queueName bindingsDirectory = (artemisDir / "bindings").toString()
log.info("Configuring address $queueName") journalDirectory = (artemisDir / "journal").toString()
val queueConfig = CoreQueueConfiguration().apply { largeMessagesDirectory = (artemisDir / "large-messages").toString()
address = queueName acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config))
name = queueName // Enable built in message deduplication. Note we still have to do our own as the delayed commits
routingType = RoutingType.ANYCAST // and our own definition of commit mean that the built in deduplication cannot remove all duplicates.
isExclusive = true idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
} isPersistIDCache = true
CoreAddressConfiguration().apply { isPopulateValidatedUser = true
name = queueName journalBufferSize_NIO = maxMessageSize + JOURNAL_HEADER_SIZE // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
queueConfigurations = listOf(queueConfig) journalBufferSize_AIO = maxMessageSize + JOURNAL_HEADER_SIZE // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store.
addRoutingType(RoutingType.ANYCAST) journalFileSize = maxMessageSize + JOURNAL_HEADER_SIZE// The size of each journal file in bytes. Artemis default is 10MiB.
} managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS)
// JMX enablement
if (config.jmxMonitoringHttpPort != null) {
isJMXManagementEnabled = true
isJMXUseBrokerName = true
} }
return SecureArtemisConfiguration().apply {
val artemisDir = config.baseDirectory / "artemis"
bindingsDirectory = (artemisDir / "bindings").toString()
journalDirectory = (artemisDir / "journal").toString()
largeMessagesDirectory = (artemisDir / "large-messages").toString()
acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config))
// Enable built in message deduplication. Note we still have to do our own as the delayed commits
// and our own definition of commit mean that the built in deduplication cannot remove all duplicates.
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
isPersistIDCache = true
isPopulateValidatedUser = true
journalBufferSize_NIO = maxMessageSize + JOURNAL_HEADER_SIZE // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
journalBufferSize_AIO = maxMessageSize + JOURNAL_HEADER_SIZE // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store.
journalFileSize = maxMessageSize + JOURNAL_HEADER_SIZE// The size of each journal file in bytes. Artemis default is 10MiB.
managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS)
connectionTtlCheckInterval = config.enterpriseConfiguration.tuning.brokerConnectionTtlCheckIntervalMs
addressConfigurations = addressConfigs
// JMX enablement }.configureAddressSecurity()
if (config.jmxMonitoringHttpPort != null) {
isJMXManagementEnabled = true
isJMXUseBrokerName = true
}
}.configureAddressSecurity()
}
/** /**
* Authenticated clients connecting to us fall in one of the following groups: * Authenticated clients connecting to us fall in one of the following groups:

View File

@ -293,20 +293,20 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
} }
override fun validateAndGetTlsCrlIssuerCert(): X509Certificate? { override fun validateAndGetTlsCrlIssuerCert(): X509Certificate? {
config.tlsCertCrlIssuer ?: return null val tlsCertCrlIssuer = config.tlsCertCrlIssuer
val tlsCertCrlIssuerPrincipal = X500Principal(config.tlsCertCrlIssuer) tlsCertCrlIssuer ?: return null
if (principalMatchesCertificatePrincipal(tlsCertCrlIssuerPrincipal, rootCert)) { if (principalMatchesCertificatePrincipal(tlsCertCrlIssuer, rootCert)) {
return rootCert return rootCert
} }
return if (config.trustStoreFile.exists()) { return if (config.trustStoreFile.exists()) {
findMatchingCertificate(tlsCertCrlIssuerPrincipal, config.loadTrustStore()) findMatchingCertificate(tlsCertCrlIssuer, config.loadTrustStore())
} else { } else {
null null
} }
} }
override fun isTlsCrlIssuerCertRequired(): Boolean { override fun isTlsCrlIssuerCertRequired(): Boolean {
return !config.tlsCertCrlIssuer.isNullOrEmpty() return config.tlsCertCrlIssuer != null
} }
private fun findMatchingCertificate(principal: X500Principal, trustStore: X509KeyStore): X509Certificate? { private fun findMatchingCertificate(principal: X500Principal, trustStore: X509KeyStore): X509Certificate? {

View File

@ -8,7 +8,7 @@
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/ */
package net.corda.node package net.corda.node.internal.cordapp
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigException import com.typesafe.config.ConfigException
@ -16,7 +16,6 @@ import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.writeText import net.corda.core.internal.writeText
import net.corda.node.internal.cordapp.CordappConfigFileProvider
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Paths import java.nio.file.Paths

View File

@ -57,7 +57,6 @@ import org.junit.rules.RuleChain
import org.slf4j.MDC import org.slf4j.MDC
import java.security.PublicKey import java.security.PublicKey
import java.util.concurrent.Future import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
@ -221,7 +220,7 @@ class TimedFlowTests {
val stx = requestPayload.signedTransaction val stx = requestPayload.signedTransaction
subFlow(ResolveTransactionsFlow(stx, otherSideSession)) subFlow(ResolveTransactionsFlow(stx, otherSideSession))
if (TimedFlowTests.requestsReceived.getAndIncrement() == 0) { if (requestsReceived.getAndIncrement() == 0) {
logger.info("Ignoring") logger.info("Ignoring")
// Waiting forever // Waiting forever
stateMachine.suspend(FlowIORequest.WaitForLedgerCommit(SecureHash.randomSHA256()), false) stateMachine.suspend(FlowIORequest.WaitForLedgerCommit(SecureHash.randomSHA256()), false)

View File

@ -28,8 +28,9 @@ import java.net.InetAddress
import java.net.URL import java.net.URL
import java.net.URI import java.net.URI
import java.nio.file.Paths import java.nio.file.Paths
import java.util.* import javax.security.auth.x500.X500Principal
import kotlin.test.* import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NodeConfigurationImplTest { class NodeConfigurationImplTest {
@Test @Test
@ -48,13 +49,6 @@ class NodeConfigurationImplTest {
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL") assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL")
} }
@Test
fun `tlsCertCrlIssuer validation fails when misconfigured`() {
val configValidationResult = configTlsCertCrlOptions(URL("http://test.com/crl"), "Corda Root CA").validate()
assertTrue { configValidationResult.isNotEmpty() }
assertThat(configValidationResult.first()).contains("Error when parsing tlsCertCrlIssuer:")
}
@Test @Test
fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() { fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() {
val configValidationResult = configTlsCertCrlOptions(null, null, false).validate() val configValidationResult = configTlsCertCrlOptions(null, null, false).validate()
@ -265,7 +259,7 @@ class NodeConfigurationImplTest {
} }
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration { private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration {
return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer, crlCheckSoftFail = crlCheckSoftFail) return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer?.let { X500Principal(it) }, crlCheckSoftFail = crlCheckSoftFail)
} }
private fun testConfiguration(dataSourceProperties: Properties): NodeConfigurationImpl { private fun testConfiguration(dataSourceProperties: Properties): NodeConfigurationImpl {

View File

@ -10,11 +10,17 @@
package net.corda.bank package net.corda.bank
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.withoutIssuer
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS import net.corda.finance.contracts.asset.Cash
import net.corda.finance.utils.sumCash
import net.corda.testing.core.BOC_NAME
import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.node.internal.demorun.nodeRunner import net.corda.testing.node.internal.demorun.nodeRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.ClassRule import org.junit.ClassRule
import org.junit.Test import org.junit.Test
@ -27,9 +33,19 @@ class BankOfCordaCordformTest : IntegrationTest() {
@Test @Test
fun `run demo`() { fun `run demo`() {
BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodesThen { BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodesAndThen {
IssueCash.requestWebIssue(30000.POUNDS)
IssueCash.requestRpcIssue(20000.DOLLARS) IssueCash.requestRpcIssue(20000.DOLLARS)
CordaRPCClient(NetworkHostAndPort("localhost", BOC_RPC_PORT)).use(BOC_RPC_USER, BOC_RPC_PWD) {
assertThat(it.proxy.vaultQuery(Cash.State::class.java).states).isEmpty() // All of the issued cash is transferred
}
CordaRPCClient(NetworkHostAndPort("localhost", BIGCORP_RPC_PORT)).use(BIGCORP_RPC_USER, BIGCORP_RPC_PWD) {
val cashStates = it.proxy.vaultQuery(Cash.State::class.java).states.map { it.state.data }
val knownOwner = it.proxy.wellKnownPartyFromAnonymous(cashStates.map { it.owner }.toSet().single())
assertThat(knownOwner?.name).isEqualTo(BIGCORP_NAME)
val totalCash = cashStates.sumCash()
assertThat(totalCash.token.issuer.party.nameOrNull()).isEqualTo(BOC_NAME)
assertThat(totalCash.withoutIssuer()).isEqualTo(20000.DOLLARS)
}
} }
} }
} }

View File

@ -1,112 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package net.corda.bank
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.User
import org.junit.ClassRule
import org.junit.Test
class BankOfCordaRPCClientTest : IntegrationTest() {
companion object {
@ClassRule
@JvmField
val databaseSchemas = IntegrationTestSchemas(BOC_NAME.toDatabaseSchemaName(), DUMMY_NOTARY_NAME.toDatabaseSchemaName(),
BIGCORP_NAME.organisation)
}
@Test
fun `issuer flow via RPC`() {
val commonPermissions = setOf(
invokeRpc("vaultTrackByCriteria"),
invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name),
invokeRpc(CordaRPCOps::notaryIdentities)
)
driver(DriverParameters(extraCordappPackagesToScan = listOf("net.corda.finance"))) {
val bocManager = User("bocManager", "password1", permissions = setOf(
startFlow<CashIssueAndPaymentFlow>()) + commonPermissions)
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet<String>() + commonPermissions)
val (nodeBankOfCorda, nodeBigCorporation) = listOf(
startNode(providedName = BOC_NAME, rpcUsers = listOf(bocManager)),
startNode(providedName = BIGCORP_NAME, rpcUsers = listOf(bigCorpCFO))
).map { it.getOrThrow() }
// Bank of Corda RPC Client
val bocClient = CordaRPCClient(nodeBankOfCorda.rpcAddress)
val bocProxy = bocClient.start("bocManager", "password1").proxy
// Big Corporation RPC Client
val bigCorpClient = CordaRPCClient(nodeBigCorporation.rpcAddress)
val bigCorpProxy = bigCorpClient.start("bigCorpCFO", "password2").proxy
// Register for Bank of Corda Vault updates
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
val vaultUpdatesBoc = bocProxy.vaultTrackByCriteria(Cash.State::class.java, criteria).updates
// Register for Big Corporation Vault updates
val vaultUpdatesBigCorp = bigCorpProxy.vaultTrackByCriteria(Cash.State::class.java, criteria).updates
val bigCorporation = bigCorpProxy.wellKnownPartyFromX500Name(BIGCORP_NAME)!!
// Kick-off actual Issuer Flow
val anonymous = true
bocProxy.startFlow(::CashIssueAndPaymentFlow,
1000.DOLLARS, OpaqueBytes.of(1),
bigCorporation,
anonymous,
defaultNotaryIdentity).returnValue.getOrThrow()
// Check Bank of Corda Vault Updates
vaultUpdatesBoc.expectEvents {
sequence(
// ISSUE
expect { update ->
require(update.consumed.isEmpty()) { "Expected 0 consumed states, actual: $update" }
require(update.produced.size == 1) { "Expected 1 produced states, actual: $update" }
},
// MOVE
expect { update ->
require(update.consumed.size == 1) { "Expected 1 consumed states, actual: $update" }
require(update.produced.isEmpty()) { "Expected 0 produced states, actual: $update" }
}
)
}
// Check Big Corporation Vault Updates
vaultUpdatesBigCorp.expectEvents {
sequence(
// MOVE
expect { (consumed, produced) ->
require(consumed.isEmpty()) { consumed.size }
require(produced.size == 1) { produced.size }
}
)
}
}
}
}

View File

@ -31,10 +31,17 @@ import kotlin.system.exitProcess
val BIGCORP_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US") val BIGCORP_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US")
private val NOTARY_NAME = CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH") private val NOTARY_NAME = CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH")
private const val BOC_RPC_PORT = 10006 const val BOC_RPC_PORT = 10006
const val BIGCORP_RPC_PORT = 10009
private const val BOC_RPC_ADMIN_PORT = 10015 private const val BOC_RPC_ADMIN_PORT = 10015
private const val BOC_WEB_PORT = 10007 private const val BOC_WEB_PORT = 10007
const val BOC_RPC_USER = "bankUser"
const val BOC_RPC_PWD = "test"
const val BIGCORP_RPC_USER = "bigCorpUser"
const val BIGCORP_RPC_PWD = "test"
class BankOfCordaCordform : CordformDefinition() { class BankOfCordaCordform : CordformDefinition() {
init { init {
@ -57,18 +64,18 @@ class BankOfCordaCordform : CordformDefinition() {
adminAddress("localhost:$BOC_RPC_ADMIN_PORT") adminAddress("localhost:$BOC_RPC_ADMIN_PORT")
} }
webPort(BOC_WEB_PORT) webPort(BOC_WEB_PORT)
rpcUsers(User("bankUser", "test", setOf(all()))) rpcUsers(User(BOC_RPC_USER, BOC_RPC_PWD, setOf(all())))
devMode(true) devMode(true)
} }
node { node {
name(BIGCORP_NAME) name(BIGCORP_NAME)
p2pPort(10008) p2pPort(10008)
rpcSettings { rpcSettings {
address("localhost:10009") address("localhost:$BIGCORP_RPC_PORT")
adminAddress("localhost:10011") adminAddress("localhost:10011")
} }
webPort(10010) webPort(10010)
rpcUsers(User("bigCorpUser", "test", setOf(all()))) rpcUsers(User(BIGCORP_RPC_USER, BIGCORP_RPC_PWD, setOf(all())))
devMode(true) devMode(true)
} }
} }
@ -123,8 +130,7 @@ object IssueCash {
return BankOfCordaClientApi.requestRPCIssue(NetworkHostAndPort("localhost", BOC_RPC_PORT), createParams(amount, NOTARY_NAME)) return BankOfCordaClientApi.requestRPCIssue(NetworkHostAndPort("localhost", BOC_RPC_PORT), createParams(amount, NOTARY_NAME))
} }
@VisibleForTesting private fun requestWebIssue(amount: Amount<Currency>) {
fun requestWebIssue(amount: Amount<Currency>) {
BankOfCordaClientApi.requestWebIssue(NetworkHostAndPort("localhost", BOC_WEB_PORT), createParams(amount, NOTARY_NAME)) BankOfCordaClientApi.requestWebIssue(NetworkHostAndPort("localhost", BOC_WEB_PORT), createParams(amount, NOTARY_NAME))
} }

View File

@ -10,6 +10,8 @@
package net.corda.bank.api package net.corda.bank.api
import net.corda.bank.BOC_RPC_PWD
import net.corda.bank.BOC_RPC_USER
import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
@ -41,7 +43,7 @@ object BankOfCordaClientApi {
fun requestRPCIssue(rpcAddress: NetworkHostAndPort, params: IssueRequestParams): SignedTransaction { fun requestRPCIssue(rpcAddress: NetworkHostAndPort, params: IssueRequestParams): SignedTransaction {
val client = CordaRPCClient(rpcAddress) val client = CordaRPCClient(rpcAddress)
// TODO: privileged security controls required // TODO: privileged security controls required
client.start("bankUser", "test").use { connection -> client.start(BOC_RPC_USER, BOC_RPC_PWD).use { connection ->
val rpc = connection.proxy val rpc = connection.proxy
rpc.waitUntilNetworkReady().getOrThrow() rpc.waitUntilNetworkReady().getOrThrow()

View File

@ -27,7 +27,7 @@ fun CordformDefinition.nodeRunner() = CordformNodeRunner(this)
/** /**
* A node runner creates and runs nodes for a given [[CordformDefinition]]. * A node runner creates and runs nodes for a given [[CordformDefinition]].
*/ */
class CordformNodeRunner(val cordformDefinition: CordformDefinition) { class CordformNodeRunner(private val cordformDefinition: CordformDefinition) {
private var extraPackagesToScan = emptyList<String>() private var extraPackagesToScan = emptyList<String>()
/** /**
@ -55,7 +55,7 @@ class CordformNodeRunner(val cordformDefinition: CordformDefinition) {
* Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes * Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes
* and webservers are up. After execution all these processes will be terminated. * and webservers are up. After execution all these processes will be terminated.
*/ */
fun deployAndRunNodesThen(block: () -> Unit) { fun deployAndRunNodesAndThen(block: () -> Unit) {
runNodes(waitForAllNodesToFinish = false, block = block) runNodes(waitForAllNodesToFinish = false, block = block)
} }