Merge pull request #1114 from corda/merges/june-28-09-45

Merges: June 28th at 09:45
This commit is contained in:
Michele Sollecito 2018-06-28 11:45:11 +01:00 committed by GitHub
commit 992d6022d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 262 additions and 172 deletions

View File

@ -10,29 +10,36 @@
package net.corda.client.rpc
import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader
import net.corda.core.context.*
import net.corda.core.contracts.FungibleAsset
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.Party
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.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.schemas.CashSchemaV1
import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.common.internal.checkNotOnClasspath
import net.corda.testing.core.*
import net.corda.testing.node.User
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.node.internal.ProcessUtilities
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
@ -41,6 +48,10 @@ import org.junit.Before
import org.junit.ClassRule
import org.junit.Test
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.Executors
import java.util.concurrent.ScheduledExecutorService
@ -49,9 +60,11 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
private val rpcUser = User("user1", "test", permissions = setOf(all())
)
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
companion object {
val rpcUser = User("user1", "test", permissions = setOf(all()))
}
private lateinit var node: StartedNode<Node>
private lateinit var identity: Party
private lateinit var client: CordaRPCClient
@ -70,7 +83,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
override fun setUp() {
super.setUp()
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
))
identity = node.info.identityFromX500Name(ALICE_NAME)
@ -102,7 +115,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
@Test
fun `shutdown command stops the node`() {
val nodeIsShut: PublishSubject<Unit> = PublishSubject.create()
val latch = CountDownLatch(1)
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 {
override fun close() {
delegate.shutdown()
}
@ -228,19 +239,70 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
)
}
}
}
private fun checkShellNotification(info: StateMachineInfo) {
val context = info.invocationContext
assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java)
}
// 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)
private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet<Trace.InvocationId>, externalTrace: Trace?, impersonatedActor: Actor?) {
val context = info.invocationContext
assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java)
assertThat(context.externalTrace).isEqualTo(externalTrace)
assertThat(context.impersonatedActor).isEqualTo(impersonatedActor)
assertThat(context.actor?.id?.value).isEqualTo(rpcUsername)
assertThat(historicalIds).doesNotContain(context.trace.invocationId)
historicalIds.add(context.trace.invocationId)
// 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
assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java)
}
private fun checkRpcNotification(info: StateMachineInfo,
rpcUsername: String,
historicalIds: MutableSet<Trace.InvocationId>,
externalTrace: Trace?,
impersonatedActor: Actor?) {
val context = info.invocationContext
assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java)
assertThat(context.externalTrace).isEqualTo(externalTrace)
assertThat(context.impersonatedActor).isEqualTo(impersonatedActor)
assertThat(context.actor?.id?.value).isEqualTo(rpcUsername)
assertThat(historicalIds).doesNotContain(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
} catch (e: IllegalStateException) {
try {
AMQPClientSerializationScheme.initialiseSerialization()
AMQPClientSerializationScheme.initialiseSerialization(classLoader)
} catch (e: IllegalStateException) {
// 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()
)
sendExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build()
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").setDaemon(true).build()
)
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
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.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.*
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
@ -29,25 +30,26 @@ class AMQPClientSerializationScheme(
companion object {
/** Call from main only. */
fun initialiseSerialization() {
nodeSerializationEnv = createSerializationEnv()
fun initialiseSerialization(classLoader: ClassLoader? = null) {
nodeSerializationEnv = createSerializationEnv(classLoader)
}
fun createSerializationEnv(): SerializationEnvironment {
fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment {
return SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {
registerScheme(AMQPClientSerializationScheme(emptyList()))
},
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,
rpcServerContext = AMQP_RPC_SERVER_CONTEXT)
rpcServerContext = AMQP_RPC_SERVER_CONTEXT
)
}
}
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) =
magic == amqpMagic && (
target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == amqpMagic && (target == UseCase.RPCClient || target == UseCase.P2P)
}
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply {
@ -60,4 +62,4 @@ class AMQPClientSerializationScheme(
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException()
}
}
}

View File

@ -222,7 +222,7 @@ class StandaloneCordaRPClientTest {
assertEquals(1, queryResults.totalStatesAvailable)
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)
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
* 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.
*
* TODO: Implement the contract sandbox loading of the contract attachments
* */
*/
// TODO: Implement the contract sandbox loading of the contract attachments
val contract: ContractClassName,
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
val notary: Party,

View File

@ -79,7 +79,7 @@ IntelliJ
Download a sample project
^^^^^^^^^^^^^^^^^^^^^^^^^
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``
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
^^^^^^^^^^^^^^^^^^^^^^^^^
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``
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``
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
----------
The best way to check that everything is working fine is by taking a deeper look at the
:doc:`example CorDapp <tutorial-cordapp>`.
First, explore the example CorDapp you just ran :doc:`here <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
: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:
PostgreSQL
````````````````````````
``````````
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:

View File

@ -12,6 +12,7 @@ package net.corda.node.internal
import com.jcabi.manifests.Manifests
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions
import io.netty.channel.unix.Errors
import net.corda.core.cordapp.Cordapp
@ -119,8 +120,16 @@ open class NodeStartup(val args: Array<String>) {
} catch (e: UnknownConfigurationKeysException) {
logger.error(e.message)
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) {
logger.error("Exception during node configuration", e)
logger.error("Unexpected error whilst reading node configuration", e)
return false
}
val errors = conf.validate()

View File

@ -118,19 +118,21 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
if (value) field = value else throw IllegalArgumentException("Can only set to true")
}
/**
/**
* Processes an event by creating the associated transition and executing it using the given executor.
* Try to avoid using this directly, instead use [processEventsUntilFlowIsResumed] or [processEventImmediately]
* instead.
*/
@Suspendable
private fun processEvent(transitionExecutor: TransitionExecutor, event: Event): FlowContinuation {
setLoggingContext()
val stateMachine = getTransientField(TransientValues::stateMachine)
val oldState = transientState!!.value
val actionExecutor = getTransientField(TransientValues::actionExecutor)
val transition = stateMachine.transition(event, oldState)
val (continuation, newState) = transitionExecutor.executeTransition(this, oldState, event, transition, actionExecutor)
transientState = TransientReference(newState)
setLoggingContext()
return continuation
}
@ -206,6 +208,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
context.pushToLoggingContext()
MDC.put("flow-id", id.uuid.toString())
MDC.put("fiber-id", this.getId().toString())
MDC.put("thread-id", Thread.currentThread().id.toString())
}
@Suspendable
@ -363,6 +366,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
val serializationContext = TransientReference(getTransientField(TransientValues::checkpointSerializationContext))
val transaction = extractThreadLocalTransaction()
parkAndSerialize { _, _ ->
setLoggingContext()
logger.trace { "Suspended on $ioRequest" }
// Will skip checkpoint if there are any idempotent flows in the subflow stack.
@ -389,7 +393,6 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
require(continuation == FlowContinuation.ProcessEvents)
unpark(SERIALIZER_BLOCKER)
}
setLoggingContext()
return uncheckedCast(processEventsUntilFlowIsResumed(
isDbTransactionOpenOnEntry = false,
isDbTransactionOpenOnExit = true

View File

@ -133,8 +133,12 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
}
protected fun loadValue(key: K): V? {
val result = currentDBSession().find(persistentEntityClass, toPersistentEntityKey(key))
return result?.apply { currentDBSession().detach(result) }?.let(fromPersistentEntity)?.second
val session = currentDBSession()
// 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

View File

@ -159,7 +159,9 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) {
@Test
fun `test purge mid-way in a single transaction`() {
// Writes intentionally do not check the database first, so purging between read and write changes behaviour
val remapped = mapOf(Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.Fail) to Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit))
// Also, a purge after write causes the subsequent read to flush to the database, causing the read to generate a constraint violation when single threaded (in same database transaction).
val remapped = mapOf(Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.Fail) to Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit),
Scenario(true, ReadOrWrite.Write, ReadOrWrite.Read, Outcome.SuccessButErrorOnCommit, Outcome.Success) to Scenario(true, ReadOrWrite.Write, ReadOrWrite.Read, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit))
scenario = remapped[scenario] ?: scenario
prepopulateIfRequired()
val map = createMap()

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.asContextEnv
import net.corda.testing.common.internal.checkNotOnClasspath
import net.corda.testing.common.internal.testNetworkParameters
import java.io.File
import java.nio.file.Path
@ -75,18 +76,15 @@ class NodeProcess(
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault())
val defaultNetworkParameters = run {
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
NetworkParametersCopier(testNetworkParameters())
}
}
init {
try {
Class.forName("net.corda.node.Corda")
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!
checkNotOnClasspath("net.corda.node.Corda") {
"Smoke test has the node in its classpath. Please remove the offending dependency."
}
}
}

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 {
dirs 'libs'
}
jcenter()
maven {
jcenter()
url 'http://www.sparetimelabs.com/maven2'
}
}

View File

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

View File

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

View File

@ -10,10 +10,8 @@
package net.corda.demobench.model
import com.typesafe.config.Config
import com.typesafe.config.*
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.internal.copyToDirectory
import net.corda.core.internal.createDirectories
@ -55,13 +53,22 @@ data class NodeConfig(
}
fun nodeConf(): Config {
val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode).toConfig()
val rpcSettings = empty()
.withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString()))
.withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString()))
.root()
return basic.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings)
val rpcSettings: ConfigObject = empty()
.withValue("address", valueFor(rpcAddress.toString()))
.withValue("adminAddress", valueFor(rpcAdminAddress.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, 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()
@ -70,33 +77,29 @@ data class NodeConfig(
fun toWebServerConfText() = webServerConf().render()
fun serialiseAsString(): String {
return toConfig().render()
}
fun serialiseAsString(): String = toConfig().render()
private fun Config.render(): String = root().render(renderOptions)
}
private data class NodeConfigurationData(
val myLegalName: CordaX500Name,
val p2pAddress: NetworkHostAndPort,
val rpcAddress: NetworkHostAndPort,
val notary: NotaryService?,
val h2port: Int,
val rpcUsers: List<User> = listOf(NodeConfig.defaultUser),
val useTestClock: Boolean,
val detectPublicIp: Boolean,
val devMode: Boolean
val myLegalName: CordaX500Name,
val p2pAddress: NetworkHostAndPort,
val rpcAddress: NetworkHostAndPort,
val notary: NotaryService?,
val h2port: Int,
val rpcUsers: List<User> = listOf(NodeConfig.defaultUser),
val useTestClock: Boolean,
val detectPublicIp: Boolean,
val devMode: Boolean
)
private data class WebServerConfigurationData(
val myLegalName: CordaX500Name,
val rpcAddress: NetworkHostAndPort,
val webAddress: NetworkHostAndPort,
val rpcUsers: List<User>
val myLegalName: CordaX500Name,
val rpcAddress: NetworkHostAndPort,
val webAddress: NetworkHostAndPort,
val rpcUsers: List<User>
) {
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 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
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
import net.corda.webserver.WebServerConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.test.*
class NodeConfigTest {
companion object {
@ -35,23 +32,26 @@ class NodeConfigTest {
@Test
fun `reading node configuration`() {
val config = createConfig(
legalName = myLegalName,
p2pPort = 10001,
rpcPort = 40002,
rpcAdminPort = 40005,
webPort = 20001,
h2port = 30001,
notary = NotaryService(validating = false),
users = listOf(user("jenny"))
legalName = myLegalName,
p2pPort = 10001,
rpcPort = 40002,
rpcAdminPort = 40005,
webPort = 20001,
h2port = 30001,
notary = NotaryService(validating = false),
users = listOf(user("jenny"))
)
val nodeConfig = config.nodeConf()
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("reference.conf"))
.withFallback(ConfigFactory.parseMap(mapOf("devMode" to true)))
.resolve()
.withValue("baseDirectory", valueFor(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("reference.conf"))
.withFallback(ConfigFactory.parseMap(mapOf("devMode" to true)))
.resolve()
val fullConfig = nodeConfig.parseAsNodeConfiguration()
// No custom configuration is created by default.
assertFailsWith<ConfigException.Missing> { nodeConfig.getConfig("custom") }
assertEquals(myLegalName, fullConfig.myLegalName)
assertEquals(localPort(40002), fullConfig.rpcOptions.address)
assertEquals(localPort(10001), fullConfig.p2pAddress)
@ -60,25 +60,49 @@ class NodeConfigTest {
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
fun `reading webserver configuration`() {
val config = createConfig(
legalName = myLegalName,
p2pPort = 10001,
rpcPort = 40002,
rpcAdminPort = 40003,
webPort = 20001,
h2port = 30001,
notary = NotaryService(validating = false),
users = listOf(user("jenny"))
legalName = myLegalName,
p2pPort = 10001,
rpcPort = 40002,
rpcAdminPort = 40003,
webPort = 20001,
h2port = 30001,
notary = NotaryService(validating = false),
users = listOf(user("jenny"))
)
val nodeConfig = config.webServerConf()
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("web-reference.conf"))
.resolve()
.withValue("baseDirectory", valueFor(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("web-reference.conf"))
.resolve()
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(40002), webConfig.rpcAddress)
assertEquals("trustpass", webConfig.trustStorePassword)
@ -86,24 +110,26 @@ class NodeConfigTest {
}
private fun createConfig(
legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"),
p2pPort: Int = -1,
rpcPort: Int = -1,
rpcAdminPort: Int = -1,
webPort: Int = -1,
h2port: Int = -1,
notary: NotaryService?,
users: List<User> = listOf(user("guest"))
legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"),
p2pPort: Int = -1,
rpcPort: Int = -1,
rpcAdminPort: Int = -1,
webPort: Int = -1,
h2port: Int = -1,
notary: NotaryService?,
users: List<User> = listOf(user("guest")),
issuableCurrencies: List<String> = emptyList()
): NodeConfig {
return NodeConfig(
myLegalName = legalName,
p2pAddress = localPort(p2pPort),
rpcAddress = localPort(rpcPort),
rpcAdminAddress = localPort(rpcAdminPort),
webAddress = localPort(webPort),
h2port = h2port,
notary = notary,
rpcUsers = users
myLegalName = legalName,
p2pAddress = localPort(p2pPort),
rpcAddress = localPort(rpcPort),
rpcAdminAddress = localPort(rpcAdminPort),
webAddress = localPort(webPort),
h2port = h2port,
notary = notary,
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.contracts.UniqueIdentifier
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.openFuture
import net.corda.core.internal.createDirectories
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.core.messaging.*
import net.corda.tools.shell.utlities.ANSIProgressRenderer
import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer
import org.crsh.command.InvocationContext
@ -141,8 +133,7 @@ object InteractiveShell {
config["crash.ssh.port"] = configuration.sshdPort?.toString()
config["crash.auth"] = "corda"
configuration.sshHostKeyDirectory?.apply {
val sshKeysDir = configuration.sshHostKeyDirectory
sshKeysDir.createDirectories()
val sshKeysDir = configuration.sshHostKeyDirectory.createDirectories()
config["crash.ssh.keypath"] = (sshKeysDir / "hostkey.pem").toString()
config["crash.ssh.keygen"] = "true"
}
@ -285,7 +276,7 @@ object InteractiveShell {
val stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, flowClazz, om)
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
// the tracker is done with the screen.
while (!Thread.currentThread().isInterrupted) {
@ -301,11 +292,7 @@ object InteractiveShell {
}
}
}
stateObservable.returnValue.get()?.apply {
if (this !is Throwable) {
output.println("Flow completed with result: $this")
}
}
output.println("Flow completed with result: ${stateObservable.returnValue.get()}")
} catch (e: NoApplicableConstructor) {
output.println("No matching constructor found:", Color.red)
e.errors.forEach { output.println("- $it", Color.red) }