mirror of
https://github.com/corda/corda.git
synced 2025-05-12 13:33:20 +00:00
Revert "Merges: June 28th at 09:45 (#1109)"
This reverts commit e5ebf04c0f113f8151dc3e5c618a07717bc8f82d.
This commit is contained in:
parent
e5ebf04c0f
commit
bbd4b3b944
@ -10,36 +10,29 @@
|
|||||||
|
|
||||||
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.location
|
import net.corda.core.internal.packageName
|
||||||
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
|
||||||
@ -48,10 +41,6 @@ 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
|
||||||
@ -60,11 +49,9 @@ 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")) {
|
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
|
||||||
companion object {
|
private val rpcUser = User("user1", "test", permissions = setOf(all())
|
||||||
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
|
||||||
@ -83,7 +70,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
|
|||||||
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)
|
||||||
@ -115,6 +102,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
|
|||||||
|
|
||||||
@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
|
||||||
@ -161,6 +149,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
@ -239,70 +228,19 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 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
|
private fun checkShellNotification(info: StateMachineInfo) {
|
||||||
// containing Cash.State objects are not receivable by the client.
|
val context = info.invocationContext
|
||||||
//
|
assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java)
|
||||||
// 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.
|
private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet<Trace.InvocationId>, externalTrace: Trace?, impersonatedActor: Actor?) {
|
||||||
@Test
|
val context = info.invocationContext
|
||||||
fun `additional class loader used by WireTransaction when it deserialises its components`() {
|
assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java)
|
||||||
val financeLocation = Cash::class.java.location.toPath().toString()
|
assertThat(context.externalTrace).isEqualTo(externalTrace)
|
||||||
val classpathWithoutFinance = ProcessUtilities.defaultClassPath
|
assertThat(context.impersonatedActor).isEqualTo(impersonatedActor)
|
||||||
.split(pathSeparator)
|
assertThat(context.actor?.id?.value).isEqualTo(rpcUsername)
|
||||||
.filter { financeLocation !in it }
|
assertThat(historicalIds).doesNotContain(context.trace.invocationId)
|
||||||
.joinToString(pathSeparator)
|
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -305,7 +305,7 @@ class CordaRPCClient private constructor(
|
|||||||
effectiveSerializationEnv
|
effectiveSerializationEnv
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
try {
|
try {
|
||||||
AMQPClientSerializationScheme.initialiseSerialization(classLoader)
|
AMQPClientSerializationScheme.initialiseSerialization()
|
||||||
} 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").setDaemon(true).build()
|
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build()
|
||||||
)
|
)
|
||||||
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
|
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
|
||||||
this::reapObservablesAndNotify,
|
this::reapObservablesAndNotify,
|
||||||
|
@ -3,7 +3,6 @@ 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
|
||||||
@ -30,26 +29,25 @@ class AMQPClientSerializationScheme(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** Call from main only. */
|
/** Call from main only. */
|
||||||
fun initialiseSerialization(classLoader: ClassLoader? = null) {
|
fun initialiseSerialization() {
|
||||||
nodeSerializationEnv = createSerializationEnv(classLoader)
|
nodeSerializationEnv = createSerializationEnv()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment {
|
fun createSerializationEnv(): SerializationEnvironment {
|
||||||
return SerializationEnvironmentImpl(
|
return SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||||
},
|
},
|
||||||
storageContext = AMQP_STORAGE_CONTEXT,
|
storageContext = AMQP_STORAGE_CONTEXT,
|
||||||
p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,
|
p2pContext = 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): Boolean {
|
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) =
|
||||||
return magic == amqpMagic && (target == UseCase.RPCClient || target == UseCase.P2P)
|
magic == amqpMagic && (
|
||||||
}
|
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 {
|
||||||
@ -62,4 +60,4 @@ class AMQPClientSerializationScheme(
|
|||||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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, true, notaryNodeIdentity).returnValue.getOrThrow()
|
rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, 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,8 +34,9 @@ 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,11 +193,31 @@ 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
|
||||||
----------
|
----------
|
||||||
First, explore the example CorDapp you just ran :doc:`here <tutorial-cordapp>`.
|
The best way to check that everything is working fine is by taking a deeper look at the
|
||||||
|
:doc:`example CorDapp <tutorial-cordapp>`.
|
||||||
|
|
||||||
Next, read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.
|
Next, you should 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,7 +12,6 @@ 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
|
||||||
@ -120,16 +119,8 @@ 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("Unexpected error whilst reading node configuration", e)
|
logger.error("Exception during node configuration", e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val errors = conf.validate()
|
val errors = conf.validate()
|
||||||
|
@ -118,21 +118,19 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
if (value) field = value else throw IllegalArgumentException("Can only set to true")
|
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.
|
* 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]
|
* Try to avoid using this directly, instead use [processEventsUntilFlowIsResumed] or [processEventImmediately]
|
||||||
* instead.
|
* instead.
|
||||||
*/
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun processEvent(transitionExecutor: TransitionExecutor, event: Event): FlowContinuation {
|
private fun processEvent(transitionExecutor: TransitionExecutor, event: Event): FlowContinuation {
|
||||||
setLoggingContext()
|
|
||||||
val stateMachine = getTransientField(TransientValues::stateMachine)
|
val stateMachine = getTransientField(TransientValues::stateMachine)
|
||||||
val oldState = transientState!!.value
|
val oldState = transientState!!.value
|
||||||
val actionExecutor = getTransientField(TransientValues::actionExecutor)
|
val actionExecutor = getTransientField(TransientValues::actionExecutor)
|
||||||
val transition = stateMachine.transition(event, oldState)
|
val transition = stateMachine.transition(event, oldState)
|
||||||
val (continuation, newState) = transitionExecutor.executeTransition(this, oldState, event, transition, actionExecutor)
|
val (continuation, newState) = transitionExecutor.executeTransition(this, oldState, event, transition, actionExecutor)
|
||||||
transientState = TransientReference(newState)
|
transientState = TransientReference(newState)
|
||||||
setLoggingContext()
|
|
||||||
return continuation
|
return continuation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +206,6 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
context.pushToLoggingContext()
|
context.pushToLoggingContext()
|
||||||
MDC.put("flow-id", id.uuid.toString())
|
MDC.put("flow-id", id.uuid.toString())
|
||||||
MDC.put("fiber-id", this.getId().toString())
|
MDC.put("fiber-id", this.getId().toString())
|
||||||
MDC.put("thread-id", Thread.currentThread().id.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -366,7 +363,6 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
val serializationContext = TransientReference(getTransientField(TransientValues::checkpointSerializationContext))
|
val serializationContext = TransientReference(getTransientField(TransientValues::checkpointSerializationContext))
|
||||||
val transaction = extractThreadLocalTransaction()
|
val transaction = extractThreadLocalTransaction()
|
||||||
parkAndSerialize { _, _ ->
|
parkAndSerialize { _, _ ->
|
||||||
setLoggingContext()
|
|
||||||
logger.trace { "Suspended on $ioRequest" }
|
logger.trace { "Suspended on $ioRequest" }
|
||||||
|
|
||||||
// Will skip checkpoint if there are any idempotent flows in the subflow stack.
|
// Will skip checkpoint if there are any idempotent flows in the subflow stack.
|
||||||
@ -393,6 +389,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
require(continuation == FlowContinuation.ProcessEvents)
|
require(continuation == FlowContinuation.ProcessEvents)
|
||||||
unpark(SERIALIZER_BLOCKER)
|
unpark(SERIALIZER_BLOCKER)
|
||||||
}
|
}
|
||||||
|
setLoggingContext()
|
||||||
return uncheckedCast(processEventsUntilFlowIsResumed(
|
return uncheckedCast(processEventsUntilFlowIsResumed(
|
||||||
isDbTransactionOpenOnEntry = false,
|
isDbTransactionOpenOnEntry = false,
|
||||||
isDbTransactionOpenOnExit = true
|
isDbTransactionOpenOnExit = true
|
||||||
|
@ -133,12 +133,8 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fun loadValue(key: K): V? {
|
protected fun loadValue(key: K): V? {
|
||||||
val session = currentDBSession()
|
val result = currentDBSession().find(persistentEntityClass, toPersistentEntityKey(key))
|
||||||
// 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.
|
return result?.apply { currentDBSession().detach(result) }?.let(fromPersistentEntity)?.second
|
||||||
// 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
|
||||||
|
@ -159,9 +159,7 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) {
|
|||||||
@Test
|
@Test
|
||||||
fun `test purge mid-way in a single transaction`() {
|
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
|
// Writes intentionally do not check the database first, so purging between read and write changes behaviour
|
||||||
// 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))
|
||||||
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
|
scenario = remapped[scenario] ?: scenario
|
||||||
prepopulateIfRequired()
|
prepopulateIfRequired()
|
||||||
val map = createMap()
|
val map = createMap()
|
||||||
|
@ -84,11 +84,11 @@ open class SerializerFactory(
|
|||||||
|
|
||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
constructor(whitelist: ClassWhitelist,
|
constructor(whitelist: ClassWhitelist,
|
||||||
carpenterClassLoader: ClassLoader,
|
classLoader: ClassLoader,
|
||||||
lenientCarpenter: Boolean = false,
|
lenientCarpenter: Boolean = false,
|
||||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||||
fingerPrinter: FingerPrinter = SerializerFingerPrinter()
|
fingerPrinter: FingerPrinter = SerializerFingerPrinter()
|
||||||
) : this(whitelist, ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
|
) : this(whitelist, ClassCarpenterImpl(whitelist, classLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
fingerPrinter.setOwner(this)
|
fingerPrinter.setOwner(this)
|
||||||
|
@ -11,16 +11,17 @@
|
|||||||
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, classpath, jdwpPort, extraJvmArguments, null, null)
|
return startJavaProcessImpl(C::class.java.name, arguments, defaultClassPath, jdwpPort, extraJvmArguments, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startCordaProcess(
|
fun startCordaProcess(
|
||||||
|
@ -18,7 +18,6 @@ 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
|
||||||
@ -76,15 +75,18 @@ 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 {
|
||||||
// TODO There are no notaries in the network parameters for smoke test nodes. If this is required then we would
|
// 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 {
|
||||||
checkNotOnClasspath("net.corda.node.Corda") {
|
try {
|
||||||
"Smoke test has the node in its classpath. Please remove the offending dependency."
|
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!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
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'
|
||||||
}
|
}
|
||||||
jcenter()
|
|
||||||
maven {
|
maven {
|
||||||
|
jcenter()
|
||||||
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 (
|
||||||
"%JAVA_HOME%\bin\javac" -O -d "%BUILDDIR%" "%%j"
|
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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
"%JAVA_HOME%\bin\jar" uvf %1 -C "%BUILDDIR%" .
|
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,8 +10,10 @@
|
|||||||
|
|
||||||
package net.corda.demobench.model
|
package net.corda.demobench.model
|
||||||
|
|
||||||
import com.typesafe.config.*
|
import com.typesafe.config.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
|
||||||
@ -53,22 +55,13 @@ data class NodeConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun nodeConf(): Config {
|
fun nodeConf(): Config {
|
||||||
val rpcSettings: ConfigObject = empty()
|
|
||||||
.withValue("address", valueFor(rpcAddress.toString()))
|
val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode).toConfig()
|
||||||
.withValue("adminAddress", valueFor(rpcAdminAddress.toString()))
|
val rpcSettings = empty()
|
||||||
.root()
|
.withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString()))
|
||||||
val customMap: Map<String, Any> = HashMap<String, Any>().also {
|
.withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString()))
|
||||||
if (issuableCurrencies.isNotEmpty()) {
|
.root()
|
||||||
it["issuableCurrencies"] = issuableCurrencies
|
return basic.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings)
|
||||||
}
|
|
||||||
}
|
|
||||||
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()
|
||||||
@ -77,29 +70,33 @@ data class NodeConfig(
|
|||||||
|
|
||||||
fun toWebServerConfText() = webServerConf().render()
|
fun toWebServerConfText() = webServerConf().render()
|
||||||
|
|
||||||
fun serialiseAsString(): String = toConfig().render()
|
fun serialiseAsString(): String {
|
||||||
|
|
||||||
|
return toConfig().render()
|
||||||
|
}
|
||||||
|
|
||||||
private fun Config.render(): String = root().render(renderOptions)
|
private fun Config.render(): String = root().render(renderOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class NodeConfigurationData(
|
private data class NodeConfigurationData(
|
||||||
val myLegalName: CordaX500Name,
|
val myLegalName: CordaX500Name,
|
||||||
val p2pAddress: NetworkHostAndPort,
|
val p2pAddress: NetworkHostAndPort,
|
||||||
val rpcAddress: NetworkHostAndPort,
|
val rpcAddress: NetworkHostAndPort,
|
||||||
val notary: NotaryService?,
|
val notary: NotaryService?,
|
||||||
val h2port: Int,
|
val h2port: Int,
|
||||||
val rpcUsers: List<User> = listOf(NodeConfig.defaultUser),
|
val rpcUsers: List<User> = listOf(NodeConfig.defaultUser),
|
||||||
val useTestClock: Boolean,
|
val useTestClock: Boolean,
|
||||||
val detectPublicIp: Boolean,
|
val detectPublicIp: Boolean,
|
||||||
val devMode: Boolean
|
val devMode: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class WebServerConfigurationData(
|
private data class WebServerConfigurationData(
|
||||||
val myLegalName: CordaX500Name,
|
val myLegalName: CordaX500Name,
|
||||||
val rpcAddress: NetworkHostAndPort,
|
val rpcAddress: NetworkHostAndPort,
|
||||||
val webAddress: NetworkHostAndPort,
|
val webAddress: NetworkHostAndPort,
|
||||||
val rpcUsers: List<User>
|
val rpcUsers: List<User>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun asConfig() = toConfig()
|
fun asConfig() = toConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,9 +127,3 @@ 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,18 +10,21 @@
|
|||||||
|
|
||||||
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.*
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class NodeConfigTest {
|
class NodeConfigTest {
|
||||||
companion object {
|
companion object {
|
||||||
@ -32,26 +35,23 @@ class NodeConfigTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `reading node configuration`() {
|
fun `reading node configuration`() {
|
||||||
val config = createConfig(
|
val config = createConfig(
|
||||||
legalName = myLegalName,
|
legalName = myLegalName,
|
||||||
p2pPort = 10001,
|
p2pPort = 10001,
|
||||||
rpcPort = 40002,
|
rpcPort = 40002,
|
||||||
rpcAdminPort = 40005,
|
rpcAdminPort = 40005,
|
||||||
webPort = 20001,
|
webPort = 20001,
|
||||||
h2port = 30001,
|
h2port = 30001,
|
||||||
notary = NotaryService(validating = false),
|
notary = NotaryService(validating = false),
|
||||||
users = listOf(user("jenny"))
|
users = listOf(user("jenny"))
|
||||||
)
|
)
|
||||||
|
|
||||||
val nodeConfig = config.nodeConf()
|
val nodeConfig = config.nodeConf()
|
||||||
.withValue("baseDirectory", valueFor(baseDir.toString()))
|
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(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,49 +60,25 @@ 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(
|
||||||
legalName = myLegalName,
|
legalName = myLegalName,
|
||||||
p2pPort = 10001,
|
p2pPort = 10001,
|
||||||
rpcPort = 40002,
|
rpcPort = 40002,
|
||||||
rpcAdminPort = 40003,
|
rpcAdminPort = 40003,
|
||||||
webPort = 20001,
|
webPort = 20001,
|
||||||
h2port = 30001,
|
h2port = 30001,
|
||||||
notary = NotaryService(validating = false),
|
notary = NotaryService(validating = false),
|
||||||
users = listOf(user("jenny"))
|
users = listOf(user("jenny"))
|
||||||
)
|
)
|
||||||
|
|
||||||
val nodeConfig = config.webServerConf()
|
val nodeConfig = config.webServerConf()
|
||||||
.withValue("baseDirectory", valueFor(baseDir.toString()))
|
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(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)
|
||||||
@ -110,26 +86,24 @@ class NodeConfigTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createConfig(
|
private fun createConfig(
|
||||||
legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"),
|
legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"),
|
||||||
p2pPort: Int = -1,
|
p2pPort: Int = -1,
|
||||||
rpcPort: Int = -1,
|
rpcPort: Int = -1,
|
||||||
rpcAdminPort: Int = -1,
|
rpcAdminPort: Int = -1,
|
||||||
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,
|
||||||
p2pAddress = localPort(p2pPort),
|
p2pAddress = localPort(p2pPort),
|
||||||
rpcAddress = localPort(rpcPort),
|
rpcAddress = localPort(rpcPort),
|
||||||
rpcAdminAddress = localPort(rpcAdminPort),
|
rpcAdminAddress = localPort(rpcAdminPort),
|
||||||
webAddress = localPort(webPort),
|
webAddress = localPort(webPort),
|
||||||
h2port = h2port,
|
h2port = h2port,
|
||||||
notary = notary,
|
notary = notary,
|
||||||
rpcUsers = users,
|
rpcUsers = users
|
||||||
issuableCurrencies = issuableCurrencies
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,10 +25,18 @@ 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.*
|
import net.corda.core.internal.Emoji
|
||||||
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.messaging.*
|
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.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
|
||||||
@ -133,7 +141,8 @@ 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.createDirectories()
|
val sshKeysDir = configuration.sshHostKeyDirectory
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
@ -276,7 +285,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) {
|
||||||
@ -292,7 +301,11 @@ object InteractiveShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output.println("Flow completed with result: ${stateObservable.returnValue.get()}")
|
stateObservable.returnValue.get()?.apply {
|
||||||
|
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