mirror of
https://github.com/corda/corda.git
synced 2025-05-09 20:12:56 +00:00
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:
commit
b09368a17a
@ -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,6 +239,29 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
private fun checkShellNotification(info: StateMachineInfo) {
|
||||||
@ -235,7 +269,11 @@ private fun checkShellNotification(info: StateMachineInfo) {
|
|||||||
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)
|
||||||
@ -244,3 +282,27 @@ private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, hi
|
|||||||
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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.
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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!
|
||||||
|
}
|
||||||
|
}
|
@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user