mirror of
https://github.com/corda/corda.git
synced 2025-03-14 00:06:45 +00:00
Merges: June 28th at 09:45 (#1109)
This commit is contained in:
parent
f663a84377
commit
e5ebf04c0f
@ -10,29 +10,36 @@
|
||||
|
||||
package net.corda.client.rpc
|
||||
|
||||
import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader
|
||||
import net.corda.core.context.*
|
||||
import net.corda.core.contracts.FungibleAsset
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.flatMap
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.internal.location
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.getCashBalance
|
||||
import net.corda.finance.contracts.getCashBalances
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.services.Permissions.Companion.all
|
||||
import net.corda.testing.common.internal.checkNotOnClasspath
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
import net.corda.testing.node.internal.NodeBasedTest
|
||||
import net.corda.testing.node.internal.ProcessUtilities
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
@ -41,6 +48,10 @@ import org.junit.Before
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.File.pathSeparator
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
@ -49,9 +60,11 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
|
||||
private val rpcUser = User("user1", "test", permissions = setOf(all())
|
||||
)
|
||||
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
|
||||
companion object {
|
||||
val rpcUser = User("user1", "test", permissions = setOf(all()))
|
||||
}
|
||||
|
||||
private lateinit var node: StartedNode<Node>
|
||||
private lateinit var identity: Party
|
||||
private lateinit var client: CordaRPCClient
|
||||
@ -70,7 +83,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser))
|
||||
client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!, CordaRPCClientConfiguration.DEFAULT.copy(
|
||||
client = CordaRPCClient(node.internals.configuration.rpcOptions.address, CordaRPCClientConfiguration.DEFAULT.copy(
|
||||
maxReconnectAttempts = 5
|
||||
))
|
||||
identity = node.info.identityFromX500Name(ALICE_NAME)
|
||||
@ -102,7 +115,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
|
||||
@Test
|
||||
fun `shutdown command stops the node`() {
|
||||
|
||||
val nodeIsShut: PublishSubject<Unit> = PublishSubject.create()
|
||||
val latch = CountDownLatch(1)
|
||||
var successful = false
|
||||
@ -149,7 +161,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
}
|
||||
|
||||
private class CloseableExecutor(private val delegate: ScheduledExecutorService) : AutoCloseable, ScheduledExecutorService by delegate {
|
||||
|
||||
override fun close() {
|
||||
delegate.shutdown()
|
||||
}
|
||||
@ -228,19 +239,70 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkShellNotification(info: StateMachineInfo) {
|
||||
val context = info.invocationContext
|
||||
assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java)
|
||||
}
|
||||
// WireTransaction stores its components as blobs which are deserialised in its constructor. This test makes sure
|
||||
// the extra class loader given to the CordaRPCClient is used in this deserialisation, as otherwise any WireTransaction
|
||||
// containing Cash.State objects are not receivable by the client.
|
||||
//
|
||||
// We run the client in a separate process, without the finance module on its system classpath to ensure that the
|
||||
// additional class loader that we give it is used. Cash.State objects are used as they can't be synthesised fully
|
||||
// by the carpenter, and thus avoiding any false-positive results.
|
||||
@Test
|
||||
fun `additional class loader used by WireTransaction when it deserialises its components`() {
|
||||
val financeLocation = Cash::class.java.location.toPath().toString()
|
||||
val classpathWithoutFinance = ProcessUtilities.defaultClassPath
|
||||
.split(pathSeparator)
|
||||
.filter { financeLocation !in it }
|
||||
.joinToString(pathSeparator)
|
||||
|
||||
private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet<Trace.InvocationId>, externalTrace: Trace?, impersonatedActor: Actor?) {
|
||||
val context = info.invocationContext
|
||||
assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java)
|
||||
assertThat(context.externalTrace).isEqualTo(externalTrace)
|
||||
assertThat(context.impersonatedActor).isEqualTo(impersonatedActor)
|
||||
assertThat(context.actor?.id?.value).isEqualTo(rpcUsername)
|
||||
assertThat(historicalIds).doesNotContain(context.trace.invocationId)
|
||||
historicalIds.add(context.trace.invocationId)
|
||||
// Create a Cash.State object for the StandaloneCashRpcClient to get
|
||||
node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
|
||||
val outOfProcessRpc = ProcessUtilities.startJavaProcess<StandaloneCashRpcClient>(
|
||||
classpath = classpathWithoutFinance,
|
||||
arguments = listOf(node.internals.configuration.rpcOptions.address.toString(), financeLocation)
|
||||
)
|
||||
assertThat(outOfProcessRpc.waitFor()).isZero() // i.e. no exceptions were thrown
|
||||
}
|
||||
|
||||
private fun checkShellNotification(info: StateMachineInfo) {
|
||||
val context = info.invocationContext
|
||||
assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java)
|
||||
}
|
||||
|
||||
private fun checkRpcNotification(info: StateMachineInfo,
|
||||
rpcUsername: String,
|
||||
historicalIds: MutableSet<Trace.InvocationId>,
|
||||
externalTrace: Trace?,
|
||||
impersonatedActor: Actor?) {
|
||||
val context = info.invocationContext
|
||||
assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java)
|
||||
assertThat(context.externalTrace).isEqualTo(externalTrace)
|
||||
assertThat(context.impersonatedActor).isEqualTo(impersonatedActor)
|
||||
assertThat(context.actor?.id?.value).isEqualTo(rpcUsername)
|
||||
assertThat(historicalIds).doesNotContain(context.trace.invocationId)
|
||||
historicalIds.add(context.trace.invocationId)
|
||||
}
|
||||
|
||||
private object StandaloneCashRpcClient {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
checkNotOnClasspath("net.corda.finance.contracts.asset.Cash") {
|
||||
"The finance module cannot be on the system classpath"
|
||||
}
|
||||
val address = NetworkHostAndPort.parse(args[0])
|
||||
val financeClassLoader = URLClassLoader(arrayOf(Paths.get(args[1]).toUri().toURL()))
|
||||
val rpcUser = CordaRPCClientTest.rpcUser
|
||||
val client = createCordaRPCClientWithSslAndClassLoader(address, classLoader = financeClassLoader)
|
||||
val state = client.use(rpcUser.username, rpcUser.password) {
|
||||
// financeClassLoader should be allowing the Cash.State to materialise
|
||||
@Suppress("DEPRECATION")
|
||||
it.proxy.internalVerifiedTransactionsSnapshot()[0].tx.outputsOfType<FungibleAsset<*>>()[0]
|
||||
}
|
||||
assertThat(state.javaClass.name).isEqualTo("net.corda.finance.contracts.asset.Cash${'$'}State")
|
||||
assertThat(state.amount.quantity).isEqualTo(10000)
|
||||
assertThat(state.amount.token.product).isEqualTo(Currency.getInstance("GBP"))
|
||||
// This particular check assures us that the Cash.State we have hasn't been carpented.
|
||||
assertThat(state.participants).isEqualTo(listOf(state.owner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ class CordaRPCClient private constructor(
|
||||
effectiveSerializationEnv
|
||||
} catch (e: IllegalStateException) {
|
||||
try {
|
||||
AMQPClientSerializationScheme.initialiseSerialization()
|
||||
AMQPClientSerializationScheme.initialiseSerialization(classLoader)
|
||||
} catch (e: IllegalStateException) {
|
||||
// Race e.g. two of these constructed in parallel, ignore.
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ class RPCClientProxyHandler(
|
||||
ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build()
|
||||
)
|
||||
sendExecutor = Executors.newSingleThreadExecutor(
|
||||
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build()
|
||||
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").setDaemon(true).build()
|
||||
)
|
||||
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
|
||||
this::reapObservablesAndNotify,
|
||||
|
@ -3,6 +3,7 @@ package net.corda.client.rpc.internal.serialization.amqp
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationContext.*
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
@ -29,25 +30,26 @@ class AMQPClientSerializationScheme(
|
||||
|
||||
companion object {
|
||||
/** Call from main only. */
|
||||
fun initialiseSerialization() {
|
||||
nodeSerializationEnv = createSerializationEnv()
|
||||
fun initialiseSerialization(classLoader: ClassLoader? = null) {
|
||||
nodeSerializationEnv = createSerializationEnv(classLoader)
|
||||
}
|
||||
|
||||
fun createSerializationEnv(): SerializationEnvironment {
|
||||
fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment {
|
||||
return SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||
},
|
||||
storageContext = AMQP_STORAGE_CONTEXT,
|
||||
p2pContext = AMQP_P2P_CONTEXT,
|
||||
p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,
|
||||
rpcClientContext = AMQP_RPC_CLIENT_CONTEXT,
|
||||
rpcServerContext = AMQP_RPC_SERVER_CONTEXT)
|
||||
rpcServerContext = AMQP_RPC_SERVER_CONTEXT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) =
|
||||
magic == amqpMagic && (
|
||||
target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
|
||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||
return magic == amqpMagic && (target == UseCase.RPCClient || target == UseCase.P2P)
|
||||
}
|
||||
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply {
|
||||
@ -60,4 +62,4 @@ class AMQPClientSerializationScheme(
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ class StandaloneCordaRPClientTest {
|
||||
assertEquals(1, queryResults.totalStatesAvailable)
|
||||
assertEquals(queryResults.states.first().state.data.amount.quantity, 629.POUNDS.quantity)
|
||||
|
||||
rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity).returnValue.getOrThrow()
|
||||
rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity, true, notaryNodeIdentity).returnValue.getOrThrow()
|
||||
|
||||
val moreResults = rpcProxy.vaultQueryBy<Cash.State>(criteria, paging, sorting)
|
||||
assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100
|
||||
|
@ -34,9 +34,8 @@ data class TransactionState<out T : ContractState> @JvmOverloads constructor(
|
||||
* Currently these are loaded from the classpath of the node which includes the cordapp directory - at some
|
||||
* point these will also be loaded and run from the attachment store directly, allowing contracts to be
|
||||
* sent across, and run, from the network from within a sandbox environment.
|
||||
*
|
||||
* TODO: Implement the contract sandbox loading of the contract attachments
|
||||
* */
|
||||
*/
|
||||
// TODO: Implement the contract sandbox loading of the contract attachments
|
||||
val contract: ContractClassName,
|
||||
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
||||
val notary: Party,
|
||||
|
@ -79,7 +79,7 @@ IntelliJ
|
||||
Download a sample project
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
1. Open a command prompt
|
||||
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
|
||||
2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example``
|
||||
3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example``
|
||||
4. Checkout the branch for Corda Enterprise 3.0.0 by running ``git checkout release-enterprise-V3``
|
||||
|
||||
@ -147,7 +147,7 @@ IntelliJ
|
||||
Download a sample project
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
1. Open a terminal
|
||||
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
|
||||
2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example``
|
||||
3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example``
|
||||
4. Checkout the branch for Corda Enterprise 3.0.0 by running ``git checkout release-enterprise-V3``
|
||||
|
||||
@ -193,31 +193,11 @@ Run from IntelliJ
|
||||
7. Wait until the run windows displays the message ``Webserver started up in XX.X sec``
|
||||
8. Confirm that the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/
|
||||
|
||||
Corda source code
|
||||
-----------------
|
||||
|
||||
The Corda platform source code is available here:
|
||||
|
||||
https://github.com/corda/corda.git
|
||||
|
||||
A CorDapp template that you can use as the basis for your own CorDapps is available in both Java and Kotlin versions:
|
||||
|
||||
https://github.com/corda/cordapp-template-java.git
|
||||
|
||||
https://github.com/corda/cordapp-template-kotlin.git
|
||||
|
||||
And a list of simple sample CorDapps for you to explore basic concepts is available here:
|
||||
|
||||
https://www.corda.net/samples/
|
||||
|
||||
You can clone these repos to your local machine by running the command ``git clone [repo URL]``.
|
||||
|
||||
Next steps
|
||||
----------
|
||||
The best way to check that everything is working fine is by taking a deeper look at the
|
||||
:doc:`example CorDapp <tutorial-cordapp>`.
|
||||
First, explore the example CorDapp you just ran :doc:`here <tutorial-cordapp>`.
|
||||
|
||||
Next, you should read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.
|
||||
Next, read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.
|
||||
|
||||
By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the
|
||||
:doc:`Hello, World tutorial <hello-world-introduction>`. You may want to refer to the API documentation, the
|
||||
|
@ -290,7 +290,7 @@ To delete existing data from the database, run the following SQL:
|
||||
.. _postgres_ref:
|
||||
|
||||
PostgreSQL
|
||||
````````````````````````
|
||||
``````````
|
||||
Corda has been tested on PostgreSQL 9.6 database, using PostgreSQL JDBC Driver 42.1.4.
|
||||
|
||||
To set up a database schema, use the following SQL:
|
||||
|
@ -12,6 +12,7 @@ package net.corda.node.internal
|
||||
|
||||
import com.jcabi.manifests.Manifests
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
@ -119,8 +120,16 @@ open class NodeStartup(val args: Array<String>) {
|
||||
} catch (e: UnknownConfigurationKeysException) {
|
||||
logger.error(e.message)
|
||||
return false
|
||||
} catch (e: ConfigException.IO) {
|
||||
println("""
|
||||
Unable to load the node config file from '${cmdlineOptions.configFile}'.
|
||||
|
||||
Try experimenting with the --base-directory flag to change which directory the node
|
||||
is looking in, or use the --config-file flag to specify it explicitly.
|
||||
""".trimIndent())
|
||||
return false
|
||||
} catch (e: Exception) {
|
||||
logger.error("Exception during node configuration", e)
|
||||
logger.error("Unexpected error whilst reading node configuration", e)
|
||||
return false
|
||||
}
|
||||
val errors = conf.validate()
|
||||
|
@ -118,19 +118,21 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
if (value) field = value else throw IllegalArgumentException("Can only set to true")
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Processes an event by creating the associated transition and executing it using the given executor.
|
||||
* Try to avoid using this directly, instead use [processEventsUntilFlowIsResumed] or [processEventImmediately]
|
||||
* instead.
|
||||
*/
|
||||
@Suspendable
|
||||
private fun processEvent(transitionExecutor: TransitionExecutor, event: Event): FlowContinuation {
|
||||
setLoggingContext()
|
||||
val stateMachine = getTransientField(TransientValues::stateMachine)
|
||||
val oldState = transientState!!.value
|
||||
val actionExecutor = getTransientField(TransientValues::actionExecutor)
|
||||
val transition = stateMachine.transition(event, oldState)
|
||||
val (continuation, newState) = transitionExecutor.executeTransition(this, oldState, event, transition, actionExecutor)
|
||||
transientState = TransientReference(newState)
|
||||
setLoggingContext()
|
||||
return continuation
|
||||
}
|
||||
|
||||
@ -206,6 +208,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
context.pushToLoggingContext()
|
||||
MDC.put("flow-id", id.uuid.toString())
|
||||
MDC.put("fiber-id", this.getId().toString())
|
||||
MDC.put("thread-id", Thread.currentThread().id.toString())
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@ -363,6 +366,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
val serializationContext = TransientReference(getTransientField(TransientValues::checkpointSerializationContext))
|
||||
val transaction = extractThreadLocalTransaction()
|
||||
parkAndSerialize { _, _ ->
|
||||
setLoggingContext()
|
||||
logger.trace { "Suspended on $ioRequest" }
|
||||
|
||||
// Will skip checkpoint if there are any idempotent flows in the subflow stack.
|
||||
@ -389,7 +393,6 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
require(continuation == FlowContinuation.ProcessEvents)
|
||||
unpark(SERIALIZER_BLOCKER)
|
||||
}
|
||||
setLoggingContext()
|
||||
return uncheckedCast(processEventsUntilFlowIsResumed(
|
||||
isDbTransactionOpenOnEntry = false,
|
||||
isDbTransactionOpenOnExit = true
|
||||
|
@ -133,8 +133,12 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
|
||||
}
|
||||
|
||||
protected fun loadValue(key: K): V? {
|
||||
val result = currentDBSession().find(persistentEntityClass, toPersistentEntityKey(key))
|
||||
return result?.apply { currentDBSession().detach(result) }?.let(fromPersistentEntity)?.second
|
||||
val session = currentDBSession()
|
||||
// IMPORTANT: The flush is needed because detach() makes the queue of unflushed entries invalid w.r.t. Hibernate internal state if the found entity is unflushed.
|
||||
// We want the detach() so that we rely on our cache memory management and don't retain strong references in the Hibernate session.
|
||||
session.flush()
|
||||
val result = session.find(persistentEntityClass, toPersistentEntityKey(key))
|
||||
return result?.apply { session.detach(result) }?.let(fromPersistentEntity)?.second
|
||||
}
|
||||
|
||||
operator fun contains(key: K) = get(key) != null
|
||||
|
@ -159,7 +159,9 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) {
|
||||
@Test
|
||||
fun `test purge mid-way in a single transaction`() {
|
||||
// Writes intentionally do not check the database first, so purging between read and write changes behaviour
|
||||
val remapped = mapOf(Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.Fail) to Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit))
|
||||
// Also, a purge after write causes the subsequent read to flush to the database, causing the read to generate a constraint violation when single threaded (in same database transaction).
|
||||
val remapped = mapOf(Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.Success, Outcome.Fail) to Scenario(true, ReadOrWrite.Read, ReadOrWrite.Write, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit),
|
||||
Scenario(true, ReadOrWrite.Write, ReadOrWrite.Read, Outcome.SuccessButErrorOnCommit, Outcome.Success) to Scenario(true, ReadOrWrite.Write, ReadOrWrite.Read, Outcome.SuccessButErrorOnCommit, Outcome.SuccessButErrorOnCommit))
|
||||
scenario = remapped[scenario] ?: scenario
|
||||
prepopulateIfRequired()
|
||||
val map = createMap()
|
||||
|
@ -84,11 +84,11 @@ open class SerializerFactory(
|
||||
|
||||
@DeleteForDJVM
|
||||
constructor(whitelist: ClassWhitelist,
|
||||
classLoader: ClassLoader,
|
||||
carpenterClassLoader: ClassLoader,
|
||||
lenientCarpenter: Boolean = false,
|
||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
fingerPrinter: FingerPrinter = SerializerFingerPrinter()
|
||||
) : this(whitelist, ClassCarpenterImpl(whitelist, classLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
|
||||
) : this(whitelist, ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
|
||||
|
||||
init {
|
||||
fingerPrinter.setOwner(this)
|
||||
|
@ -11,17 +11,16 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import java.io.File.pathSeparator
|
||||
import java.nio.file.Path
|
||||
|
||||
object ProcessUtilities {
|
||||
inline fun <reified C : Any> startJavaProcess(
|
||||
arguments: List<String>,
|
||||
classpath: String = defaultClassPath,
|
||||
jdwpPort: Int? = null,
|
||||
extraJvmArguments: List<String> = emptyList()
|
||||
): Process {
|
||||
return startJavaProcessImpl(C::class.java.name, arguments, defaultClassPath, jdwpPort, extraJvmArguments, null, null)
|
||||
return startJavaProcessImpl(C::class.java.name, arguments, classpath, jdwpPort, extraJvmArguments, null, null)
|
||||
}
|
||||
|
||||
fun startCordaProcess(
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.asContextEnv
|
||||
import net.corda.testing.common.internal.checkNotOnClasspath
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@ -75,18 +76,15 @@ class NodeProcess(
|
||||
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault())
|
||||
val defaultNetworkParameters = run {
|
||||
AMQPClientSerializationScheme.createSerializationEnv().asContextEnv {
|
||||
// There are no notaries in the network parameters for smoke test nodes. If this is required then we would
|
||||
// TODO There are no notaries in the network parameters for smoke test nodes. If this is required then we would
|
||||
// need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork
|
||||
NetworkParametersCopier(testNetworkParameters())
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
try {
|
||||
Class.forName("net.corda.node.Corda")
|
||||
throw Error("Smoke test has the node in its classpath. Please remove the offending dependency.")
|
||||
} catch (e: ClassNotFoundException) {
|
||||
// If the class can't be found then we're good!
|
||||
checkNotOnClasspath("net.corda.node.Corda") {
|
||||
"Smoke test has the node in its classpath. Please remove the offending dependency."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
dirs 'libs'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
jcenter()
|
||||
url 'http://www.sparetimelabs.com/maven2'
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||
call %DIRNAME%\..\..\gradlew -PpackageType=exe javapackage %*
|
||||
if ERRORLEVEL 1 goto Fail
|
||||
@echo
|
||||
@echo Wrote installer to %DIRNAME%\build\javapackage\bundles\
|
||||
@echo Wrote installer to %DIRNAME%build\javapackage\bundles\
|
||||
@echo
|
||||
goto end
|
||||
|
||||
|
@ -29,14 +29,14 @@ if exist "%BUILDDIR%" rmdir /s /q "%BUILDDIR%"
|
||||
mkdir "%BUILDDIR%"
|
||||
|
||||
for /r "%SOURCEDIR%" %%j in (*.java) do (
|
||||
javac -O -d "%BUILDDIR%" "%%j"
|
||||
"%JAVA_HOME%\bin\javac" -O -d "%BUILDDIR%" "%%j"
|
||||
if ERRORLEVEL 1 (
|
||||
@echo "Failed to compile %%j"
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
jar uvf %1 -C "%BUILDDIR%" .
|
||||
"%JAVA_HOME%\bin\jar" uvf %1 -C "%BUILDDIR%" .
|
||||
if ERRORLEVEL 1 (
|
||||
@echo "Failed to update %1"
|
||||
exit /b 1
|
||||
|
@ -10,10 +10,8 @@
|
||||
|
||||
package net.corda.demobench.model
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.*
|
||||
import com.typesafe.config.ConfigFactory.empty
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.copyToDirectory
|
||||
import net.corda.core.internal.createDirectories
|
||||
@ -55,13 +53,22 @@ data class NodeConfig(
|
||||
}
|
||||
|
||||
fun nodeConf(): Config {
|
||||
|
||||
val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode).toConfig()
|
||||
val rpcSettings = empty()
|
||||
.withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString()))
|
||||
.withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString()))
|
||||
.root()
|
||||
return basic.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings)
|
||||
val rpcSettings: ConfigObject = empty()
|
||||
.withValue("address", valueFor(rpcAddress.toString()))
|
||||
.withValue("adminAddress", valueFor(rpcAdminAddress.toString()))
|
||||
.root()
|
||||
val customMap: Map<String, Any> = HashMap<String, Any>().also {
|
||||
if (issuableCurrencies.isNotEmpty()) {
|
||||
it["issuableCurrencies"] = issuableCurrencies
|
||||
}
|
||||
}
|
||||
val custom: ConfigObject = ConfigFactory.parseMap(customMap).root()
|
||||
return NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode)
|
||||
.toConfig()
|
||||
.withoutPath("rpcAddress")
|
||||
.withoutPath("rpcAdminAddress")
|
||||
.withValue("rpcSettings", rpcSettings)
|
||||
.withOptionalValue("custom", custom)
|
||||
}
|
||||
|
||||
fun webServerConf() = WebServerConfigurationData(myLegalName, rpcAddress, webAddress, rpcUsers).asConfig()
|
||||
@ -70,33 +77,29 @@ data class NodeConfig(
|
||||
|
||||
fun toWebServerConfText() = webServerConf().render()
|
||||
|
||||
fun serialiseAsString(): String {
|
||||
|
||||
return toConfig().render()
|
||||
}
|
||||
fun serialiseAsString(): String = toConfig().render()
|
||||
|
||||
private fun Config.render(): String = root().render(renderOptions)
|
||||
}
|
||||
|
||||
private data class NodeConfigurationData(
|
||||
val myLegalName: CordaX500Name,
|
||||
val p2pAddress: NetworkHostAndPort,
|
||||
val rpcAddress: NetworkHostAndPort,
|
||||
val notary: NotaryService?,
|
||||
val h2port: Int,
|
||||
val rpcUsers: List<User> = listOf(NodeConfig.defaultUser),
|
||||
val useTestClock: Boolean,
|
||||
val detectPublicIp: Boolean,
|
||||
val devMode: Boolean
|
||||
val myLegalName: CordaX500Name,
|
||||
val p2pAddress: NetworkHostAndPort,
|
||||
val rpcAddress: NetworkHostAndPort,
|
||||
val notary: NotaryService?,
|
||||
val h2port: Int,
|
||||
val rpcUsers: List<User> = listOf(NodeConfig.defaultUser),
|
||||
val useTestClock: Boolean,
|
||||
val detectPublicIp: Boolean,
|
||||
val devMode: Boolean
|
||||
)
|
||||
|
||||
private data class WebServerConfigurationData(
|
||||
val myLegalName: CordaX500Name,
|
||||
val rpcAddress: NetworkHostAndPort,
|
||||
val webAddress: NetworkHostAndPort,
|
||||
val rpcUsers: List<User>
|
||||
val myLegalName: CordaX500Name,
|
||||
val rpcAddress: NetworkHostAndPort,
|
||||
val webAddress: NetworkHostAndPort,
|
||||
val rpcUsers: List<User>
|
||||
) {
|
||||
|
||||
fun asConfig() = toConfig()
|
||||
}
|
||||
|
||||
@ -127,3 +130,9 @@ data class NodeConfigWrapper(val baseDir: Path, val nodeConfig: NodeConfig) : Ha
|
||||
fun user(name: String) = User(name, "letmein", setOf("ALL"))
|
||||
|
||||
fun String.toKey() = filter { !it.isWhitespace() }.toLowerCase()
|
||||
|
||||
fun <T> valueFor(any: T): ConfigValue = ConfigValueFactory.fromAnyRef(any)
|
||||
|
||||
private fun Config.withOptionalValue(path: String, obj: ConfigObject): Config {
|
||||
return if (obj.isEmpty()) this else this.withValue(path, obj)
|
||||
}
|
||||
|
@ -10,21 +10,18 @@
|
||||
|
||||
package net.corda.demobench.model
|
||||
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
|
||||
import net.corda.webserver.WebServerConfig
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
class NodeConfigTest {
|
||||
companion object {
|
||||
@ -35,23 +32,26 @@ class NodeConfigTest {
|
||||
@Test
|
||||
fun `reading node configuration`() {
|
||||
val config = createConfig(
|
||||
legalName = myLegalName,
|
||||
p2pPort = 10001,
|
||||
rpcPort = 40002,
|
||||
rpcAdminPort = 40005,
|
||||
webPort = 20001,
|
||||
h2port = 30001,
|
||||
notary = NotaryService(validating = false),
|
||||
users = listOf(user("jenny"))
|
||||
legalName = myLegalName,
|
||||
p2pPort = 10001,
|
||||
rpcPort = 40002,
|
||||
rpcAdminPort = 40005,
|
||||
webPort = 20001,
|
||||
h2port = 30001,
|
||||
notary = NotaryService(validating = false),
|
||||
users = listOf(user("jenny"))
|
||||
)
|
||||
|
||||
val nodeConfig = config.nodeConf()
|
||||
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString()))
|
||||
.withFallback(ConfigFactory.parseResources("reference.conf"))
|
||||
.withFallback(ConfigFactory.parseMap(mapOf("devMode" to true)))
|
||||
.resolve()
|
||||
.withValue("baseDirectory", valueFor(baseDir.toString()))
|
||||
.withFallback(ConfigFactory.parseResources("reference.conf"))
|
||||
.withFallback(ConfigFactory.parseMap(mapOf("devMode" to true)))
|
||||
.resolve()
|
||||
val fullConfig = nodeConfig.parseAsNodeConfiguration()
|
||||
|
||||
// No custom configuration is created by default.
|
||||
assertFailsWith<ConfigException.Missing> { nodeConfig.getConfig("custom") }
|
||||
|
||||
assertEquals(myLegalName, fullConfig.myLegalName)
|
||||
assertEquals(localPort(40002), fullConfig.rpcOptions.address)
|
||||
assertEquals(localPort(10001), fullConfig.p2pAddress)
|
||||
@ -60,25 +60,49 @@ class NodeConfigTest {
|
||||
assertFalse(fullConfig.detectPublicIp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reading node configuration with currencies`() {
|
||||
val config = createConfig(
|
||||
legalName = myLegalName,
|
||||
p2pPort = 10001,
|
||||
rpcPort = 10002,
|
||||
rpcAdminPort = 10003,
|
||||
webPort = 10004,
|
||||
h2port = 10005,
|
||||
notary = NotaryService(validating = false),
|
||||
issuableCurrencies = listOf("GBP")
|
||||
)
|
||||
|
||||
val nodeConfig = config.nodeConf()
|
||||
.withValue("baseDirectory", valueFor(baseDir.toString()))
|
||||
.withFallback(ConfigFactory.parseResources("reference.conf"))
|
||||
.resolve()
|
||||
val custom = nodeConfig.getConfig("custom")
|
||||
assertEquals(listOf("GBP"), custom.getAnyRefList("issuableCurrencies"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reading webserver configuration`() {
|
||||
val config = createConfig(
|
||||
legalName = myLegalName,
|
||||
p2pPort = 10001,
|
||||
rpcPort = 40002,
|
||||
rpcAdminPort = 40003,
|
||||
webPort = 20001,
|
||||
h2port = 30001,
|
||||
notary = NotaryService(validating = false),
|
||||
users = listOf(user("jenny"))
|
||||
legalName = myLegalName,
|
||||
p2pPort = 10001,
|
||||
rpcPort = 40002,
|
||||
rpcAdminPort = 40003,
|
||||
webPort = 20001,
|
||||
h2port = 30001,
|
||||
notary = NotaryService(validating = false),
|
||||
users = listOf(user("jenny"))
|
||||
)
|
||||
|
||||
val nodeConfig = config.webServerConf()
|
||||
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString()))
|
||||
.withFallback(ConfigFactory.parseResources("web-reference.conf"))
|
||||
.resolve()
|
||||
.withValue("baseDirectory", valueFor(baseDir.toString()))
|
||||
.withFallback(ConfigFactory.parseResources("web-reference.conf"))
|
||||
.resolve()
|
||||
val webConfig = WebServerConfig(baseDir, nodeConfig)
|
||||
|
||||
// No custom configuration is created by default.
|
||||
assertFailsWith<ConfigException.Missing> { nodeConfig.getConfig("custom") }
|
||||
|
||||
assertEquals(localPort(20001), webConfig.webAddress)
|
||||
assertEquals(localPort(40002), webConfig.rpcAddress)
|
||||
assertEquals("trustpass", webConfig.trustStorePassword)
|
||||
@ -86,24 +110,26 @@ class NodeConfigTest {
|
||||
}
|
||||
|
||||
private fun createConfig(
|
||||
legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"),
|
||||
p2pPort: Int = -1,
|
||||
rpcPort: Int = -1,
|
||||
rpcAdminPort: Int = -1,
|
||||
webPort: Int = -1,
|
||||
h2port: Int = -1,
|
||||
notary: NotaryService?,
|
||||
users: List<User> = listOf(user("guest"))
|
||||
legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"),
|
||||
p2pPort: Int = -1,
|
||||
rpcPort: Int = -1,
|
||||
rpcAdminPort: Int = -1,
|
||||
webPort: Int = -1,
|
||||
h2port: Int = -1,
|
||||
notary: NotaryService?,
|
||||
users: List<User> = listOf(user("guest")),
|
||||
issuableCurrencies: List<String> = emptyList()
|
||||
): NodeConfig {
|
||||
return NodeConfig(
|
||||
myLegalName = legalName,
|
||||
p2pAddress = localPort(p2pPort),
|
||||
rpcAddress = localPort(rpcPort),
|
||||
rpcAdminAddress = localPort(rpcAdminPort),
|
||||
webAddress = localPort(webPort),
|
||||
h2port = h2port,
|
||||
notary = notary,
|
||||
rpcUsers = users
|
||||
myLegalName = legalName,
|
||||
p2pAddress = localPort(p2pPort),
|
||||
rpcAddress = localPort(rpcPort),
|
||||
rpcAdminAddress = localPort(rpcAdminPort),
|
||||
webAddress = localPort(webPort),
|
||||
h2port = h2port,
|
||||
notary = notary,
|
||||
rpcUsers = users,
|
||||
issuableCurrencies = issuableCurrencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -25,18 +25,10 @@ import net.corda.core.CordaException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.internal.Emoji
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.rootCause
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
import net.corda.core.messaging.StateMachineUpdate
|
||||
import net.corda.core.messaging.pendingFlowsCount
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.tools.shell.utlities.ANSIProgressRenderer
|
||||
import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer
|
||||
import org.crsh.command.InvocationContext
|
||||
@ -141,8 +133,7 @@ object InteractiveShell {
|
||||
config["crash.ssh.port"] = configuration.sshdPort?.toString()
|
||||
config["crash.auth"] = "corda"
|
||||
configuration.sshHostKeyDirectory?.apply {
|
||||
val sshKeysDir = configuration.sshHostKeyDirectory
|
||||
sshKeysDir.createDirectories()
|
||||
val sshKeysDir = configuration.sshHostKeyDirectory.createDirectories()
|
||||
config["crash.ssh.keypath"] = (sshKeysDir / "hostkey.pem").toString()
|
||||
config["crash.ssh.keygen"] = "true"
|
||||
}
|
||||
@ -285,7 +276,7 @@ object InteractiveShell {
|
||||
val stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, flowClazz, om)
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
ansiProgressRenderer.render(stateObservable, { latch.countDown() })
|
||||
ansiProgressRenderer.render(stateObservable, latch::countDown)
|
||||
// Wait for the flow to end and the progress tracker to notice. By the time the latch is released
|
||||
// the tracker is done with the screen.
|
||||
while (!Thread.currentThread().isInterrupted) {
|
||||
@ -301,11 +292,7 @@ object InteractiveShell {
|
||||
}
|
||||
}
|
||||
}
|
||||
stateObservable.returnValue.get()?.apply {
|
||||
if (this !is Throwable) {
|
||||
output.println("Flow completed with result: $this")
|
||||
}
|
||||
}
|
||||
output.println("Flow completed with result: ${stateObservable.returnValue.get()}")
|
||||
} catch (e: NoApplicableConstructor) {
|
||||
output.println("No matching constructor found:", Color.red)
|
||||
e.errors.forEach { output.println("- $it", Color.red) }
|
||||
|
Loading…
x
Reference in New Issue
Block a user