Merge remote-tracking branch 'remotes/open/master' into merges/june-28-09-45

# Conflicts:
#	docs/source/getting-set-up.rst
#	docs/source/node-database.rst
This commit is contained in:
sollecitom 2018-06-28 09:51:11 +01:00
commit b09368a17a
20 changed files with 254 additions and 169 deletions

View File

@ -10,29 +10,36 @@
package net.corda.client.rpc package net.corda.client.rpc
import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader
import net.corda.core.context.* import net.corda.core.context.*
import net.corda.core.contracts.FungibleAsset
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.packageName import net.corda.core.internal.location
import net.corda.core.internal.toPath
import net.corda.core.messaging.* import net.corda.core.messaging.*
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance import net.corda.finance.contracts.getCashBalance
import net.corda.finance.contracts.getCashBalances import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.schemas.CashSchemaV1
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.common.internal.checkNotOnClasspath
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.internal.NodeBasedTest import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.node.internal.ProcessUtilities
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
@ -41,6 +48,10 @@ import org.junit.Before
import org.junit.ClassRule import org.junit.ClassRule
import org.junit.Test import org.junit.Test
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.io.File.pathSeparator
import java.net.URLClassLoader
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
@ -49,9 +60,11 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) { class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
private val rpcUser = User("user1", "test", permissions = setOf(all()) companion object {
) val rpcUser = User("user1", "test", permissions = setOf(all()))
}
private lateinit var node: StartedNode<Node> private lateinit var node: StartedNode<Node>
private lateinit var identity: Party private lateinit var identity: Party
private lateinit var client: CordaRPCClient private lateinit var client: CordaRPCClient
@ -70,7 +83,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser)) node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser))
client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!, CordaRPCClientConfiguration.DEFAULT.copy( client = CordaRPCClient(node.internals.configuration.rpcOptions.address, CordaRPCClientConfiguration.DEFAULT.copy(
maxReconnectAttempts = 5 maxReconnectAttempts = 5
)) ))
identity = node.info.identityFromX500Name(ALICE_NAME) identity = node.info.identityFromX500Name(ALICE_NAME)
@ -102,7 +115,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
@Test @Test
fun `shutdown command stops the node`() { fun `shutdown command stops the node`() {
val nodeIsShut: PublishSubject<Unit> = PublishSubject.create() val nodeIsShut: PublishSubject<Unit> = PublishSubject.create()
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
var successful = false var successful = false
@ -149,7 +161,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
} }
private class CloseableExecutor(private val delegate: ScheduledExecutorService) : AutoCloseable, ScheduledExecutorService by delegate { private class CloseableExecutor(private val delegate: ScheduledExecutorService) : AutoCloseable, ScheduledExecutorService by delegate {
override fun close() { override fun close() {
delegate.shutdown() delegate.shutdown()
} }
@ -228,14 +239,41 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
) )
} }
} }
}
private fun checkShellNotification(info: StateMachineInfo) { // WireTransaction stores its components as blobs which are deserialised in its constructor. This test makes sure
// the extra class loader given to the CordaRPCClient is used in this deserialisation, as otherwise any WireTransaction
// containing Cash.State objects are not receivable by the client.
//
// We run the client in a separate process, without the finance module on its system classpath to ensure that the
// additional class loader that we give it is used. Cash.State objects are used as they can't be synthesised fully
// by the carpenter, and thus avoiding any false-positive results.
@Test
fun `additional class loader used by WireTransaction when it deserialises its components`() {
val financeLocation = Cash::class.java.location.toPath().toString()
val classpathWithoutFinance = ProcessUtilities.defaultClassPath
.split(pathSeparator)
.filter { financeLocation !in it }
.joinToString(pathSeparator)
// Create a Cash.State object for the StandaloneCashRpcClient to get
node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
val outOfProcessRpc = ProcessUtilities.startJavaProcess<StandaloneCashRpcClient>(
classpath = classpathWithoutFinance,
arguments = listOf(node.internals.configuration.rpcOptions.address.toString(), financeLocation)
)
assertThat(outOfProcessRpc.waitFor()).isZero() // i.e. no exceptions were thrown
}
private fun checkShellNotification(info: StateMachineInfo) {
val context = info.invocationContext val context = info.invocationContext
assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java) assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java)
} }
private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet<Trace.InvocationId>, externalTrace: Trace?, impersonatedActor: Actor?) { private fun checkRpcNotification(info: StateMachineInfo,
rpcUsername: String,
historicalIds: MutableSet<Trace.InvocationId>,
externalTrace: Trace?,
impersonatedActor: Actor?) {
val context = info.invocationContext val context = info.invocationContext
assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java) assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java)
assertThat(context.externalTrace).isEqualTo(externalTrace) assertThat(context.externalTrace).isEqualTo(externalTrace)
@ -243,4 +281,28 @@ private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, hi
assertThat(context.actor?.id?.value).isEqualTo(rpcUsername) assertThat(context.actor?.id?.value).isEqualTo(rpcUsername)
assertThat(historicalIds).doesNotContain(context.trace.invocationId) assertThat(historicalIds).doesNotContain(context.trace.invocationId)
historicalIds.add(context.trace.invocationId) historicalIds.add(context.trace.invocationId)
}
private object StandaloneCashRpcClient {
@JvmStatic
fun main(args: Array<String>) {
checkNotOnClasspath("net.corda.finance.contracts.asset.Cash") {
"The finance module cannot be on the system classpath"
}
val address = NetworkHostAndPort.parse(args[0])
val financeClassLoader = URLClassLoader(arrayOf(Paths.get(args[1]).toUri().toURL()))
val rpcUser = CordaRPCClientTest.rpcUser
val client = createCordaRPCClientWithSslAndClassLoader(address, classLoader = financeClassLoader)
val state = client.use(rpcUser.username, rpcUser.password) {
// financeClassLoader should be allowing the Cash.State to materialise
@Suppress("DEPRECATION")
it.proxy.internalVerifiedTransactionsSnapshot()[0].tx.outputsOfType<FungibleAsset<*>>()[0]
}
assertThat(state.javaClass.name).isEqualTo("net.corda.finance.contracts.asset.Cash${'$'}State")
assertThat(state.amount.quantity).isEqualTo(10000)
assertThat(state.amount.token.product).isEqualTo(Currency.getInstance("GBP"))
// This particular check assures us that the Cash.State we have hasn't been carpented.
assertThat(state.participants).isEqualTo(listOf(state.owner))
}
}
} }

View File

@ -305,7 +305,7 @@ class CordaRPCClient private constructor(
effectiveSerializationEnv effectiveSerializationEnv
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
try { try {
AMQPClientSerializationScheme.initialiseSerialization() AMQPClientSerializationScheme.initialiseSerialization(classLoader)
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
// Race e.g. two of these constructed in parallel, ignore. // Race e.g. two of these constructed in parallel, ignore.
} }

View File

@ -211,7 +211,7 @@ class RPCClientProxyHandler(
ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build() ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build()
) )
sendExecutor = Executors.newSingleThreadExecutor( sendExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build() ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").setDaemon(true).build()
) )
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate( reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
this::reapObservablesAndNotify, this::reapObservablesAndNotify,

View File

@ -3,6 +3,7 @@ package net.corda.client.rpc.internal.serialization.amqp
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.*
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.SerializationEnvironmentImpl
@ -29,25 +30,26 @@ class AMQPClientSerializationScheme(
companion object { companion object {
/** Call from main only. */ /** Call from main only. */
fun initialiseSerialization() { fun initialiseSerialization(classLoader: ClassLoader? = null) {
nodeSerializationEnv = createSerializationEnv() nodeSerializationEnv = createSerializationEnv(classLoader)
} }
fun createSerializationEnv(): SerializationEnvironment { fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment {
return SerializationEnvironmentImpl( return SerializationEnvironmentImpl(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {
registerScheme(AMQPClientSerializationScheme(emptyList())) registerScheme(AMQPClientSerializationScheme(emptyList()))
}, },
storageContext = AMQP_STORAGE_CONTEXT, storageContext = AMQP_STORAGE_CONTEXT,
p2pContext = AMQP_P2P_CONTEXT, p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,
rpcClientContext = AMQP_RPC_CLIENT_CONTEXT, rpcClientContext = AMQP_RPC_CLIENT_CONTEXT,
rpcServerContext = AMQP_RPC_SERVER_CONTEXT) rpcServerContext = AMQP_RPC_SERVER_CONTEXT
)
} }
} }
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) = override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
magic == amqpMagic && ( return magic == amqpMagic && (target == UseCase.RPCClient || target == UseCase.P2P)
target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P) }
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply { return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply {

View File

@ -222,7 +222,7 @@ class StandaloneCordaRPClientTest {
assertEquals(1, queryResults.totalStatesAvailable) assertEquals(1, queryResults.totalStatesAvailable)
assertEquals(queryResults.states.first().state.data.amount.quantity, 629.POUNDS.quantity) assertEquals(queryResults.states.first().state.data.amount.quantity, 629.POUNDS.quantity)
rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity).returnValue.getOrThrow() rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity, true, notaryNodeIdentity).returnValue.getOrThrow()
val moreResults = rpcProxy.vaultQueryBy<Cash.State>(criteria, paging, sorting) val moreResults = rpcProxy.vaultQueryBy<Cash.State>(criteria, paging, sorting)
assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100 assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100

View File

@ -34,9 +34,8 @@ data class TransactionState<out T : ContractState> @JvmOverloads constructor(
* Currently these are loaded from the classpath of the node which includes the cordapp directory - at some * Currently these are loaded from the classpath of the node which includes the cordapp directory - at some
* point these will also be loaded and run from the attachment store directly, allowing contracts to be * point these will also be loaded and run from the attachment store directly, allowing contracts to be
* sent across, and run, from the network from within a sandbox environment. * sent across, and run, from the network from within a sandbox environment.
* */
* TODO: Implement the contract sandbox loading of the contract attachments // TODO: Implement the contract sandbox loading of the contract attachments
* */
val contract: ContractClassName, val contract: ContractClassName,
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */ /** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
val notary: Party, val notary: Party,

View File

@ -79,7 +79,7 @@ IntelliJ
Download a sample project Download a sample project
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
1. Open a command prompt 1. Open a command prompt
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example`` 2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example``
3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example`` 3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example``
4. Checkout the branch for Corda Enterprise 3.0.0 by running ``git checkout release-enterprise-V3`` 4. Checkout the branch for Corda Enterprise 3.0.0 by running ``git checkout release-enterprise-V3``
@ -147,7 +147,7 @@ IntelliJ
Download a sample project Download a sample project
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
1. Open a terminal 1. Open a terminal
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example`` 2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example``
3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example`` 3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example``
4. Checkout the branch for Corda Enterprise 3.0.0 by running ``git checkout release-enterprise-V3`` 4. Checkout the branch for Corda Enterprise 3.0.0 by running ``git checkout release-enterprise-V3``
@ -193,31 +193,11 @@ Run from IntelliJ
7. Wait until the run windows displays the message ``Webserver started up in XX.X sec`` 7. Wait until the run windows displays the message ``Webserver started up in XX.X sec``
8. Confirm that the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/ 8. Confirm that the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/
Corda source code
-----------------
The Corda platform source code is available here:
https://github.com/corda/corda.git
A CorDapp template that you can use as the basis for your own CorDapps is available in both Java and Kotlin versions:
https://github.com/corda/cordapp-template-java.git
https://github.com/corda/cordapp-template-kotlin.git
And a list of simple sample CorDapps for you to explore basic concepts is available here:
https://www.corda.net/samples/
You can clone these repos to your local machine by running the command ``git clone [repo URL]``.
Next steps Next steps
---------- ----------
The best way to check that everything is working fine is by taking a deeper look at the First, explore the example CorDapp you just ran :doc:`here <tutorial-cordapp>`.
:doc:`example CorDapp <tutorial-cordapp>`.
Next, you should read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works. Next, read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.
By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the
:doc:`Hello, World tutorial <hello-world-introduction>`. You may want to refer to the API documentation, the :doc:`Hello, World tutorial <hello-world-introduction>`. You may want to refer to the API documentation, the

View File

@ -290,7 +290,7 @@ To delete existing data from the database, run the following SQL:
.. _postgres_ref: .. _postgres_ref:
PostgreSQL PostgreSQL
```````````````````````` ``````````
Corda has been tested on PostgreSQL 9.6 database, using PostgreSQL JDBC Driver 42.1.4. Corda has been tested on PostgreSQL 9.6 database, using PostgreSQL JDBC Driver 42.1.4.
To set up a database schema, use the following SQL: To set up a database schema, use the following SQL:

View File

@ -12,6 +12,7 @@ package net.corda.node.internal
import com.jcabi.manifests.Manifests import com.jcabi.manifests.Manifests
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
import io.netty.channel.unix.Errors import io.netty.channel.unix.Errors
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
@ -119,8 +120,16 @@ open class NodeStartup(val args: Array<String>) {
} catch (e: UnknownConfigurationKeysException) { } catch (e: UnknownConfigurationKeysException) {
logger.error(e.message) logger.error(e.message)
return false return false
} catch (e: ConfigException.IO) {
println("""
Unable to load the node config file from '${cmdlineOptions.configFile}'.
Try experimenting with the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent())
return false
} catch (e: Exception) { } catch (e: Exception) {
logger.error("Exception during node configuration", e) logger.error("Unexpected error whilst reading node configuration", e)
return false return false
} }
val errors = conf.validate() val errors = conf.validate()

View File

@ -133,8 +133,12 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
} }
protected fun loadValue(key: K): V? { protected fun loadValue(key: K): V? {
val result = currentDBSession().find(persistentEntityClass, toPersistentEntityKey(key)) val session = currentDBSession()
return result?.apply { currentDBSession().detach(result) }?.let(fromPersistentEntity)?.second // IMPORTANT: The flush is needed because detach() makes the queue of unflushed entries invalid w.r.t. Hibernate internal state if the found entity is unflushed.
// We want the detach() so that we rely on our cache memory management and don't retain strong references in the Hibernate session.
session.flush()
val result = session.find(persistentEntityClass, toPersistentEntityKey(key))
return result?.apply { session.detach(result) }?.let(fromPersistentEntity)?.second
} }
operator fun contains(key: K) = get(key) != null operator fun contains(key: K) = get(key) != null

View File

@ -84,11 +84,11 @@ open class SerializerFactory(
@DeleteForDJVM @DeleteForDJVM
constructor(whitelist: ClassWhitelist, constructor(whitelist: ClassWhitelist,
classLoader: ClassLoader, carpenterClassLoader: ClassLoader,
lenientCarpenter: Boolean = false, lenientCarpenter: Boolean = false,
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
fingerPrinter: FingerPrinter = SerializerFingerPrinter() fingerPrinter: FingerPrinter = SerializerFingerPrinter()
) : this(whitelist, ClassCarpenterImpl(whitelist, classLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter) ) : this(whitelist, ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
init { init {
fingerPrinter.setOwner(this) fingerPrinter.setOwner(this)

View File

@ -11,17 +11,16 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.exists
import java.io.File.pathSeparator
import java.nio.file.Path import java.nio.file.Path
object ProcessUtilities { object ProcessUtilities {
inline fun <reified C : Any> startJavaProcess( inline fun <reified C : Any> startJavaProcess(
arguments: List<String>, arguments: List<String>,
classpath: String = defaultClassPath,
jdwpPort: Int? = null, jdwpPort: Int? = null,
extraJvmArguments: List<String> = emptyList() extraJvmArguments: List<String> = emptyList()
): Process { ): Process {
return startJavaProcessImpl(C::class.java.name, arguments, defaultClassPath, jdwpPort, extraJvmArguments, null, null) return startJavaProcessImpl(C::class.java.name, arguments, classpath, jdwpPort, extraJvmArguments, null, null)
} }
fun startCordaProcess( fun startCordaProcess(

View File

@ -18,6 +18,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.asContextEnv import net.corda.testing.common.internal.asContextEnv
import net.corda.testing.common.internal.checkNotOnClasspath
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
@ -75,18 +76,15 @@ class NodeProcess(
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault()) val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault())
val defaultNetworkParameters = run { val defaultNetworkParameters = run {
AMQPClientSerializationScheme.createSerializationEnv().asContextEnv { AMQPClientSerializationScheme.createSerializationEnv().asContextEnv {
// There are no notaries in the network parameters for smoke test nodes. If this is required then we would // TODO There are no notaries in the network parameters for smoke test nodes. If this is required then we would
// need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork // need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork
NetworkParametersCopier(testNetworkParameters()) NetworkParametersCopier(testNetworkParameters())
} }
} }
init { init {
try { checkNotOnClasspath("net.corda.node.Corda") {
Class.forName("net.corda.node.Corda") "Smoke test has the node in its classpath. Please remove the offending dependency."
throw Error("Smoke test has the node in its classpath. Please remove the offending dependency.")
} catch (e: ClassNotFoundException) {
// If the class can't be found then we're good!
} }
} }
} }

View File

@ -0,0 +1,10 @@
package net.corda.testing.common.internal
inline fun checkNotOnClasspath(className: String, errorMessage: () -> Any) {
try {
Class.forName(className)
throw IllegalStateException(errorMessage().toString())
} catch (e: ClassNotFoundException) {
// If the class can't be found then we're good!
}
}

View File

@ -46,8 +46,8 @@ repositories {
flatDir { flatDir {
dirs 'libs' dirs 'libs'
} }
maven {
jcenter() jcenter()
maven {
url 'http://www.sparetimelabs.com/maven2' url 'http://www.sparetimelabs.com/maven2'
} }
} }

View File

@ -11,7 +11,7 @@ if "%DIRNAME%" == "" set DIRNAME=.
call %DIRNAME%\..\..\gradlew -PpackageType=exe javapackage %* call %DIRNAME%\..\..\gradlew -PpackageType=exe javapackage %*
if ERRORLEVEL 1 goto Fail if ERRORLEVEL 1 goto Fail
@echo @echo
@echo Wrote installer to %DIRNAME%\build\javapackage\bundles\ @echo Wrote installer to %DIRNAME%build\javapackage\bundles\
@echo @echo
goto end goto end

View File

@ -29,14 +29,14 @@ if exist "%BUILDDIR%" rmdir /s /q "%BUILDDIR%"
mkdir "%BUILDDIR%" mkdir "%BUILDDIR%"
for /r "%SOURCEDIR%" %%j in (*.java) do ( for /r "%SOURCEDIR%" %%j in (*.java) do (
javac -O -d "%BUILDDIR%" "%%j" "%JAVA_HOME%\bin\javac" -O -d "%BUILDDIR%" "%%j"
if ERRORLEVEL 1 ( if ERRORLEVEL 1 (
@echo "Failed to compile %%j" @echo "Failed to compile %%j"
exit /b 1 exit /b 1
) )
) )
jar uvf %1 -C "%BUILDDIR%" . "%JAVA_HOME%\bin\jar" uvf %1 -C "%BUILDDIR%" .
if ERRORLEVEL 1 ( if ERRORLEVEL 1 (
@echo "Failed to update %1" @echo "Failed to update %1"
exit /b 1 exit /b 1

View File

@ -10,10 +10,8 @@
package net.corda.demobench.model package net.corda.demobench.model
import com.typesafe.config.Config import com.typesafe.config.*
import com.typesafe.config.ConfigFactory.empty import com.typesafe.config.ConfigFactory.empty
import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.copyToDirectory import net.corda.core.internal.copyToDirectory
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
@ -55,13 +53,22 @@ data class NodeConfig(
} }
fun nodeConf(): Config { fun nodeConf(): Config {
val rpcSettings: ConfigObject = empty()
val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode).toConfig() .withValue("address", valueFor(rpcAddress.toString()))
val rpcSettings = empty() .withValue("adminAddress", valueFor(rpcAdminAddress.toString()))
.withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString()))
.withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString()))
.root() .root()
return basic.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings) 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, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode)
.toConfig()
.withoutPath("rpcAddress")
.withoutPath("rpcAdminAddress")
.withValue("rpcSettings", rpcSettings)
.withOptionalValue("custom", custom)
} }
fun webServerConf() = WebServerConfigurationData(myLegalName, rpcAddress, webAddress, rpcUsers).asConfig() fun webServerConf() = WebServerConfigurationData(myLegalName, rpcAddress, webAddress, rpcUsers).asConfig()
@ -70,10 +77,7 @@ data class NodeConfig(
fun toWebServerConfText() = webServerConf().render() fun toWebServerConfText() = webServerConf().render()
fun serialiseAsString(): String { fun serialiseAsString(): String = toConfig().render()
return toConfig().render()
}
private fun Config.render(): String = root().render(renderOptions) private fun Config.render(): String = root().render(renderOptions)
} }
@ -96,7 +100,6 @@ private data class WebServerConfigurationData(
val webAddress: NetworkHostAndPort, val webAddress: NetworkHostAndPort,
val rpcUsers: List<User> val rpcUsers: List<User>
) { ) {
fun asConfig() = toConfig() fun asConfig() = toConfig()
} }
@ -127,3 +130,9 @@ data class NodeConfigWrapper(val baseDir: Path, val nodeConfig: NodeConfig) : Ha
fun user(name: String) = User(name, "letmein", setOf("ALL")) fun user(name: String) = User(name, "letmein", setOf("ALL"))
fun String.toKey() = filter { !it.isWhitespace() }.toLowerCase() fun String.toKey() = filter { !it.isWhitespace() }.toLowerCase()
fun <T> valueFor(any: T): ConfigValue = ConfigValueFactory.fromAnyRef(any)
private fun Config.withOptionalValue(path: String, obj: ConfigObject): Config {
return if (obj.isEmpty()) this else this.withValue(path, obj)
}

View File

@ -10,21 +10,18 @@
package net.corda.demobench.model package net.corda.demobench.model
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
import net.corda.webserver.WebServerConfig import net.corda.webserver.WebServerConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.test.assertEquals import kotlin.test.*
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NodeConfigTest { class NodeConfigTest {
companion object { companion object {
@ -46,12 +43,15 @@ class NodeConfigTest {
) )
val nodeConfig = config.nodeConf() val nodeConfig = config.nodeConf()
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString())) .withValue("baseDirectory", valueFor(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("reference.conf")) .withFallback(ConfigFactory.parseResources("reference.conf"))
.withFallback(ConfigFactory.parseMap(mapOf("devMode" to true))) .withFallback(ConfigFactory.parseMap(mapOf("devMode" to true)))
.resolve() .resolve()
val fullConfig = nodeConfig.parseAsNodeConfiguration() val fullConfig = nodeConfig.parseAsNodeConfiguration()
// No custom configuration is created by default.
assertFailsWith<ConfigException.Missing> { nodeConfig.getConfig("custom") }
assertEquals(myLegalName, fullConfig.myLegalName) assertEquals(myLegalName, fullConfig.myLegalName)
assertEquals(localPort(40002), fullConfig.rpcOptions.address) assertEquals(localPort(40002), fullConfig.rpcOptions.address)
assertEquals(localPort(10001), fullConfig.p2pAddress) assertEquals(localPort(10001), fullConfig.p2pAddress)
@ -60,6 +60,27 @@ class NodeConfigTest {
assertFalse(fullConfig.detectPublicIp) assertFalse(fullConfig.detectPublicIp)
} }
@Test
fun `reading node configuration with currencies`() {
val config = createConfig(
legalName = myLegalName,
p2pPort = 10001,
rpcPort = 10002,
rpcAdminPort = 10003,
webPort = 10004,
h2port = 10005,
notary = NotaryService(validating = false),
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"))
}
@Test @Test
fun `reading webserver configuration`() { fun `reading webserver configuration`() {
val config = createConfig( val config = createConfig(
@ -74,11 +95,14 @@ class NodeConfigTest {
) )
val nodeConfig = config.webServerConf() val nodeConfig = config.webServerConf()
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString())) .withValue("baseDirectory", valueFor(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("web-reference.conf")) .withFallback(ConfigFactory.parseResources("web-reference.conf"))
.resolve() .resolve()
val webConfig = WebServerConfig(baseDir, nodeConfig) val webConfig = WebServerConfig(baseDir, nodeConfig)
// No custom configuration is created by default.
assertFailsWith<ConfigException.Missing> { nodeConfig.getConfig("custom") }
assertEquals(localPort(20001), webConfig.webAddress) assertEquals(localPort(20001), webConfig.webAddress)
assertEquals(localPort(40002), webConfig.rpcAddress) assertEquals(localPort(40002), webConfig.rpcAddress)
assertEquals("trustpass", webConfig.trustStorePassword) assertEquals("trustpass", webConfig.trustStorePassword)
@ -93,7 +117,8 @@ class NodeConfigTest {
webPort: Int = -1, webPort: Int = -1,
h2port: Int = -1, h2port: Int = -1,
notary: NotaryService?, notary: NotaryService?,
users: List<User> = listOf(user("guest")) users: List<User> = listOf(user("guest")),
issuableCurrencies: List<String> = emptyList()
): NodeConfig { ): NodeConfig {
return NodeConfig( return NodeConfig(
myLegalName = legalName, myLegalName = legalName,
@ -103,7 +128,8 @@ class NodeConfigTest {
webAddress = localPort(webPort), webAddress = localPort(webPort),
h2port = h2port, h2port = h2port,
notary = notary, notary = notary,
rpcUsers = users rpcUsers = users,
issuableCurrencies = issuableCurrencies
) )
} }

View File

@ -25,18 +25,10 @@ import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.internal.Emoji import net.corda.core.internal.*
import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.createDirectories import net.corda.core.messaging.*
import net.corda.core.internal.div
import net.corda.core.internal.rootCause
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.pendingFlowsCount
import net.corda.tools.shell.utlities.ANSIProgressRenderer import net.corda.tools.shell.utlities.ANSIProgressRenderer
import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer
import org.crsh.command.InvocationContext import org.crsh.command.InvocationContext
@ -141,8 +133,7 @@ object InteractiveShell {
config["crash.ssh.port"] = configuration.sshdPort?.toString() config["crash.ssh.port"] = configuration.sshdPort?.toString()
config["crash.auth"] = "corda" config["crash.auth"] = "corda"
configuration.sshHostKeyDirectory?.apply { configuration.sshHostKeyDirectory?.apply {
val sshKeysDir = configuration.sshHostKeyDirectory val sshKeysDir = configuration.sshHostKeyDirectory.createDirectories()
sshKeysDir.createDirectories()
config["crash.ssh.keypath"] = (sshKeysDir / "hostkey.pem").toString() config["crash.ssh.keypath"] = (sshKeysDir / "hostkey.pem").toString()
config["crash.ssh.keygen"] = "true" config["crash.ssh.keygen"] = "true"
} }
@ -285,7 +276,7 @@ object InteractiveShell {
val stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, flowClazz, om) val stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, flowClazz, om)
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
ansiProgressRenderer.render(stateObservable, { latch.countDown() }) ansiProgressRenderer.render(stateObservable, latch::countDown)
// Wait for the flow to end and the progress tracker to notice. By the time the latch is released // Wait for the flow to end and the progress tracker to notice. By the time the latch is released
// the tracker is done with the screen. // the tracker is done with the screen.
while (!Thread.currentThread().isInterrupted) { while (!Thread.currentThread().isInterrupted) {
@ -301,11 +292,7 @@ object InteractiveShell {
} }
} }
} }
stateObservable.returnValue.get()?.apply { output.println("Flow completed with result: ${stateObservable.returnValue.get()}")
if (this !is Throwable) {
output.println("Flow completed with result: $this")
}
}
} catch (e: NoApplicableConstructor) { } catch (e: NoApplicableConstructor) {
output.println("No matching constructor found:", Color.red) output.println("No matching constructor found:", Color.red)
e.errors.forEach { output.println("- $it", Color.red) } e.errors.forEach { output.println("- $it", Color.red) }