mirror of
https://github.com/corda/corda.git
synced 2025-06-22 17:09:00 +00:00
Merge branch 'release/os/4.12' into merge-release/os/4.11-release/os/4.12-2024-08-13-341
# Conflicts: # docker/src/docker/DockerfileAL # node/src/integration-test/kotlin/net/corda/node/services/identity/NotaryCertificateRotationTest.kt
This commit is contained in:
@ -3,17 +3,10 @@ ENV GRADLE_USER_HOME=/tmp/gradle
|
||||
RUN mkdir /tmp/gradle && mkdir -p /home/root/.m2/repository
|
||||
|
||||
RUN apt-get update && apt-get install -y curl libatomic1 && \
|
||||
curl -O https://cdn.azul.com/zulu/bin/zulu8.40.0.25-ca-jdk8.0.222-linux_amd64.deb && \
|
||||
apt-get install -y java-common && apt install -y ./zulu8.40.0.25-ca-jdk8.0.222-linux_amd64.deb && \
|
||||
curl -O https://cdn.azul.com/zulu/bin/zulu17.46.19-ca-jdk17.0.9-linux_amd64.deb && \
|
||||
apt-get install -y java-common && apt install -y ./zulu17.46.19-ca-jdk17.0.9-linux_amd64.deb && \
|
||||
apt-get clean && \
|
||||
rm -f zulu8.40.0.25-ca-jdk8.0.222-linux_amd64.deb && \
|
||||
curl -O https://cdn.azul.com/zulu/bin/zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz && \
|
||||
mv /zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz /usr/lib/jvm/ && \
|
||||
cd /usr/lib/jvm/ && \
|
||||
tar -zxvf zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz && \
|
||||
rm -rf zulu-8-amd64 && \
|
||||
mv zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64 zulu-8-amd64 && \
|
||||
rm -f zulu8.40.0.25-ca-fx-jdk8.0.222-linux_x64.tar.gz && \
|
||||
rm -f zulu17.46.19-ca-jdk17.0.9-linux_amd64.deb && \
|
||||
cd / && mkdir -p /tmp/source
|
||||
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
FROM stefanotestingcr.azurecr.io/buildbase:11latest
|
||||
COPY . /tmp/source
|
||||
CMD cd /tmp/source && GRADLE_USER_HOME=/tmp/gradle ./gradlew clean testClasses integrationTestClasses --parallel --info
|
17
testing/cordapps/4.11-workflows/build.gradle
Normal file
17
testing/cordapps/4.11-workflows/build.gradle
Normal file
@ -0,0 +1,17 @@
|
||||
apply plugin: 'corda.kotlin-1.2'
|
||||
|
||||
dependencies {
|
||||
compileOnly "net.corda:corda-core:4.11.+"
|
||||
compileOnly "net.corda:corda-finance-contracts:4.11.+"
|
||||
compileOnly "net.corda:corda-finance-workflows:4.11.+"
|
||||
}
|
||||
|
||||
jar {
|
||||
archiveBaseName = "4.11-workflows-cordapp"
|
||||
archiveVersion = ""
|
||||
manifest {
|
||||
// This JAR is part of Corda's testing framework.
|
||||
// Driver will not include it as part of an out-of-process node.
|
||||
attributes('Corda-Testing': true)
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.corda.testing.cordapps.workflows411
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.NotaryChangeFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
|
||||
// We need a separate flow as NotaryChangeFlow is not StartableByRPC
|
||||
@StartableByRPC
|
||||
class IssueAndChangeNotaryFlow(private val oldNotary: Party, private val newNotary: Party) : FlowLogic<SecureHash>() {
|
||||
@Suppress("MagicNumber")
|
||||
@Suspendable
|
||||
override fun call(): SecureHash {
|
||||
subFlow(CashIssueFlow(10.DOLLARS, OpaqueBytes.of(0x01), oldNotary))
|
||||
val oldState = serviceHub.vaultService.queryBy(Cash.State::class.java).states.single()
|
||||
check(oldState.state.notary == oldNotary) { oldState.state.notary }
|
||||
val newState = subFlow(NotaryChangeFlow(oldState, newNotary))
|
||||
check(newState.state.notary == newNotary) { newState.state.notary }
|
||||
val notaryChangeTx = checkNotNull(serviceHub.validatedTransactions.getTransaction(newState.ref.txhash))
|
||||
check(notaryChangeTx.coreTransaction is NotaryChangeWireTransaction) { notaryChangeTx.coreTransaction }
|
||||
return notaryChangeTx.id
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'net.corda.plugins.cordapp'
|
||||
//apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
|
||||
dependencies {
|
||||
cordaCompile project(":core")
|
||||
cordaProvided project(":core")
|
||||
cordapp project(':finance:contracts')
|
||||
cordapp project(':finance:workflows')
|
||||
|
||||
cordaProvided "org.slf4j:slf4j-api:$slf4j_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -14,6 +17,7 @@ jar {
|
||||
// Driver will not include it as part of an out-of-process node.
|
||||
attributes('Corda-Testing': true)
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
cordapp {
|
||||
@ -28,4 +32,4 @@ cordapp {
|
||||
signing {
|
||||
enabled false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
//apply plugin: 'net.corda.plugins.cordapp'
|
||||
//apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
|
||||
@ -10,7 +10,9 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
implementation project(":core")
|
||||
|
||||
api "javax.persistence:javax.persistence-api:2.2"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -20,4 +22,4 @@ jar {
|
||||
// Driver will not include it as part of an out-of-process node.
|
||||
attributes('Corda-Testing': true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'net.corda.plugins.cordapp'
|
||||
//apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
|
||||
dependencies {
|
||||
cordaCompile project(":core")
|
||||
cordaProvided project(":core")
|
||||
cordapp project(":testing:cordapps:dbfailure:dbfcontracts")
|
||||
|
||||
cordaProvided "org.hibernate:hibernate-core:$hibernate_version"
|
||||
cordaProvided "io.reactivex:rxjava:$rxjava_version"
|
||||
cordaProvided "org.slf4j:slf4j-api:$slf4j_version"
|
||||
cordaProvided "co.paralleluniverse:quasar-core:$quasar_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -28,4 +33,4 @@ cordapp {
|
||||
signing {
|
||||
enabled false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,25 +36,25 @@ object CreateStateFlow {
|
||||
}
|
||||
|
||||
fun errorTargetsToNum(vararg targets: ErrorTarget): Int {
|
||||
return targets.map { it.targetNumber }.sum()
|
||||
return targets.sumOf { it.targetNumber }
|
||||
}
|
||||
|
||||
private val targetMap = ErrorTarget.values().associateBy(ErrorTarget::targetNumber)
|
||||
|
||||
fun getServiceTarget(target: Int?): ErrorTarget {
|
||||
return target?.let { targetMap.getValue(((it/10000) % 1000)*10000) } ?: CreateStateFlow.ErrorTarget.NoError
|
||||
return target?.let { targetMap.getValue(((it/10000) % 1000)*10000) } ?: ErrorTarget.NoError
|
||||
}
|
||||
|
||||
fun getServiceExceptionHandlingTarget(target: Int?): ErrorTarget {
|
||||
return target?.let { targetMap.getValue(((it / 1000) % 10) * 1000) } ?: CreateStateFlow.ErrorTarget.NoError
|
||||
return target?.let { targetMap.getValue(((it / 1000) % 10) * 1000) } ?: ErrorTarget.NoError
|
||||
}
|
||||
|
||||
fun getTxTarget(target: Int?): ErrorTarget {
|
||||
return target?.let { targetMap.getValue(((it / 10) % 10) * 10) } ?: CreateStateFlow.ErrorTarget.NoError
|
||||
return target?.let { targetMap.getValue(((it / 10) % 10) * 10) } ?: ErrorTarget.NoError
|
||||
}
|
||||
|
||||
fun getFlowTarget(target: Int?): ErrorTarget {
|
||||
return target?.let { targetMap.getValue(((it / 100) % 10) * 100) } ?: CreateStateFlow.ErrorTarget.NoError
|
||||
return target?.let { targetMap.getValue(((it / 100) % 10) * 100) } ?: ErrorTarget.NoError
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
@ -73,7 +73,7 @@ object CreateStateFlow {
|
||||
val state = DbFailureContract.TestState(
|
||||
UniqueIdentifier(),
|
||||
listOf(ourIdentity),
|
||||
if (txTarget == CreateStateFlow.ErrorTarget.TxInvalidState) null else randomValue,
|
||||
if (txTarget == ErrorTarget.TxInvalidState) null else randomValue,
|
||||
errorTarget, ourIdentity
|
||||
)
|
||||
val txCommand = Command(DbFailureContract.Commands.Create(), ourIdentity.owningKey)
|
||||
@ -88,12 +88,11 @@ object CreateStateFlow {
|
||||
|
||||
val signedTx = serviceHub.signInitialTransaction(txBuilder)
|
||||
|
||||
@Suppress("TooGenericExceptionCaught") // this is fully intentional here, to allow twiddling with exceptions according to config
|
||||
try {
|
||||
logger.info("Test flow: recording transaction")
|
||||
serviceHub.recordTransactions(signedTx)
|
||||
} catch (t: Throwable) {
|
||||
if (getFlowTarget(errorTarget) == CreateStateFlow.ErrorTarget.FlowSwallowErrors) {
|
||||
if (getFlowTarget(errorTarget) == ErrorTarget.FlowSwallowErrors) {
|
||||
logger.info("Test flow: Swallowing all exception! Muahahaha!", t)
|
||||
} else {
|
||||
logger.info("Test flow: caught exception - rethrowing")
|
||||
|
@ -44,7 +44,6 @@ class DbListenerService(services: AppServiceHub) : SingletonSerializeAsToken() {
|
||||
|
||||
produced.forEach {
|
||||
val contractState = it.state.data as? DbFailureContract.TestState
|
||||
@Suppress("TooGenericExceptionCaught") // this is fully intentional here, to allow twiddling with exceptions
|
||||
try {
|
||||
when (CreateStateFlow.getServiceTarget(contractState?.errorTarget)) {
|
||||
CreateStateFlow.ErrorTarget.ServiceSqlSyntaxError -> {
|
||||
@ -161,7 +160,7 @@ class DbListenerService(services: AppServiceHub) : SingletonSerializeAsToken() {
|
||||
}
|
||||
|
||||
if (onError != null) {
|
||||
val onErrorWrapper: ((Throwable) -> Unit)? = {
|
||||
val onErrorWrapper: (Throwable) -> Unit = {
|
||||
onErrorVisited?.let {
|
||||
it(services.myInfo.legalIdentities.first())
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
//apply plugin: 'net.corda.plugins.cordapp'
|
||||
//apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
implementation project(":core")
|
||||
implementation "javax.persistence:javax.persistence-api:2.2"
|
||||
implementation "org.slf4j:slf4j-api:$slf4j_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -13,4 +15,4 @@ jar {
|
||||
// Driver will not include it as part of an out-of-process node.
|
||||
attributes('Corda-Testing': true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
implementation project(":core")
|
||||
implementation "co.paralleluniverse:quasar-core:$quasar_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -11,4 +12,4 @@ jar {
|
||||
// Driver will not include it as part of an out-of-process node.
|
||||
attributes('Corda-Testing': true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,39 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'net.corda.plugins.publish-utils'
|
||||
id 'net.corda.plugins.api-scanner'
|
||||
id 'com.jfrog.artifactory'
|
||||
id 'java-library'
|
||||
id 'corda.common-publishing'
|
||||
}
|
||||
|
||||
description 'Core test types and helpers for testing Corda'
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':node-api')
|
||||
implementation project(':serialization')
|
||||
api project(':test-common')
|
||||
implementation "io.netty:netty-handler-proxy:$netty_version"
|
||||
|
||||
api "org.jetbrains.kotlin:kotlin-test"
|
||||
|
||||
// Bouncy castle support needed for X509 certificate manipulation
|
||||
implementation "org.bouncycastle:bcprov-lts8on:${bouncycastle_version}"
|
||||
implementation "org.bouncycastle:bcpkix-lts8on:${bouncycastle_version}"
|
||||
|
||||
implementation "org.slf4j:slf4j-api:$slf4j_version"
|
||||
implementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
|
||||
implementation "org.mockito:mockito-core:$mockito_version"
|
||||
implementation "com.natpryce:hamkrest:$hamkrest_version"
|
||||
implementation "com.google.guava:guava-testlib:$guava_version"
|
||||
implementation "io.reactivex:rxjava:$rxjava_version"
|
||||
implementation "junit:junit:$junit_version"
|
||||
implementation("org.apache.activemq:artemis-server:${artemis_version}") {
|
||||
exclude group: 'org.apache.commons', module: 'commons-dbcp2'
|
||||
exclude group: 'org.jgroups', module: 'jgroups'
|
||||
}
|
||||
|
||||
testImplementation "org.assertj:assertj-core:${assertj_version}"
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.1'
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -23,6 +45,11 @@ jar {
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
}
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId jar.baseName
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,19 @@ import net.corda.coretesting.internal.stubs.CertificateStoreStubs
|
||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||
import net.corda.nodeapi.internal.loadDevCaTrustStore
|
||||
import net.corda.nodeapi.internal.registerDevP2pCertificates
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.fileSize
|
||||
import kotlin.io.path.inputStream
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
fun configureTestSSL(legalName: CordaX500Name): MutualSslConfiguration {
|
||||
|
||||
val certificatesDirectory = Files.createTempDirectory("certs")
|
||||
val config = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
|
||||
if (config.trustStore.getOptional() == null) {
|
||||
@ -19,3 +28,24 @@ fun configureTestSSL(legalName: CordaX500Name): MutualSslConfiguration {
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
inline fun <T> Path.useZipFile(block: (FileSystem) -> T): T {
|
||||
if (fileSize() == 0L) {
|
||||
// Need to first create an empty jar before it can be opened
|
||||
JarOutputStream(outputStream()).close()
|
||||
}
|
||||
return FileSystems.newFileSystem(this).use(block)
|
||||
}
|
||||
|
||||
inline fun <T> Path.modifyJarManifest(block: (Manifest) -> T): T? {
|
||||
return useZipFile { zipFs ->
|
||||
val manifestFile = zipFs.getPath("META-INF", "MANIFEST.MF")
|
||||
if (!manifestFile.exists()) return null
|
||||
val manifest = manifestFile.inputStream().use(::Manifest)
|
||||
val result = block(manifest)
|
||||
manifestFile.outputStream().use(manifest::write)
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fun Attributes.delete(name: String): String? = remove(Attributes.Name(name)) as String?
|
||||
|
@ -7,7 +7,7 @@ import net.corda.core.serialization.CustomSerializationScheme
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.nodeapi.internal.serialization.CustomSerializationSchemeAdapter
|
||||
import net.corda.serialization.internal.verifier.CustomSerializationSchemeAdapter
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoCheckpointSerializer
|
||||
|
@ -1,7 +1,7 @@
|
||||
@file: Suppress("MatchingDeclarationName")
|
||||
package net.corda.coretesting.internal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doAnswer
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.exceptions.base.MockitoException
|
||||
@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* A method on a mock was called, but no behaviour was previously specified for that method.
|
||||
* You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details.
|
||||
* You can use [org.mockito.kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details.
|
||||
*/
|
||||
class UndefinedMockBehaviorException(message: String) : RuntimeException(message)
|
||||
|
||||
@ -79,7 +79,7 @@ private class SpectatorDefaultAnswer : DefaultAnswer() {
|
||||
?: method.returnType!!
|
||||
}
|
||||
|
||||
private fun newSpectator(invocation: InvocationOnMock) = spectator(type)!!.also { log.info("New spectator {} for: {}", it, invocation.arguments) }
|
||||
private fun newSpectator(invocation: InvocationOnMock) = spectator(type).also { log.info("New spectator {} for: {}", it, invocation.arguments) }
|
||||
private val spectators = try {
|
||||
val first = newSpectator(invocation)
|
||||
ConcurrentHashMap<InvocationOnMock, Any>().apply { put(invocation, first) }
|
||||
|
@ -2,6 +2,7 @@ package net.corda.coretesting.internal.performance
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@ -23,7 +24,7 @@ data class Rate(
|
||||
*/
|
||||
operator fun times(inUnit: TimeUnit): Long = inUnit.convert(numberOfEvents, perTimeUnit)
|
||||
|
||||
override fun toString(): String = "$numberOfEvents / ${perTimeUnit.name.dropLast(1).toLowerCase()}" // drop the "s" at the end
|
||||
override fun toString(): String = "$numberOfEvents / ${perTimeUnit.name.dropLast(1).lowercase(Locale.getDefault())}" // drop the "s" at the end
|
||||
}
|
||||
|
||||
operator fun Long.div(timeUnit: TimeUnit) = Rate(this, timeUnit)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.coretesting.internal.stubs
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS
|
||||
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_PASS
|
||||
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS
|
||||
@ -9,6 +8,7 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||
import net.corda.nodeapi.internal.config.SslConfiguration
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import kotlin.io.path.div
|
||||
|
||||
class CertificateStoreStubs {
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.testing.core
|
||||
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.doAnswer
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.whenever
|
||||
import net.corda.core.internal.staticField
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
|
@ -1,5 +1,5 @@
|
||||
@file:JvmName("TestUtils")
|
||||
@file:Suppress("TooGenericExceptionCaught", "MagicNumber", "ComplexMethod", "LongParameterList")
|
||||
@file:Suppress("MagicNumber", "ComplexMethod", "LongParameterList")
|
||||
|
||||
package net.corda.testing.core
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.testing.core.internal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.doAnswer
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.whenever
|
||||
import net.corda.core.internal.staticField
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||
|
@ -1,8 +1,6 @@
|
||||
package net.corda.testing.core.internal
|
||||
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
|
||||
import net.corda.core.internal.delete
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.addManifest
|
||||
@ -24,6 +22,8 @@ import javax.tools.JavaFileObject
|
||||
import javax.tools.SimpleJavaFileObject
|
||||
import javax.tools.StandardLocation
|
||||
import javax.tools.ToolProvider
|
||||
import kotlin.io.path.deleteExisting
|
||||
import kotlin.io.path.div
|
||||
|
||||
object ContractJarTestUtils {
|
||||
|
||||
@ -47,8 +47,8 @@ object ContractJarTestUtils {
|
||||
val pwd = "testPassword"
|
||||
this.generateKey(alias, pwd, ALICE_NAME.toString())
|
||||
val signer = this.signJar(jarName.toAbsolutePath().toString(), alias, pwd)
|
||||
(this / "_shredder").delete()
|
||||
(this / "_teststore").delete()
|
||||
(this / "_shredder").deleteExisting()
|
||||
(this / "_teststore").deleteExisting()
|
||||
return signer
|
||||
}
|
||||
|
||||
@ -133,8 +133,8 @@ object ContractJarTestUtils {
|
||||
} else keyStoreDir
|
||||
|
||||
val signer = workingDir.signJar(jarName.toAbsolutePath().toString(), alias, pwd)
|
||||
(workingDir / "_shredder").delete()
|
||||
(workingDir / "_teststore").delete()
|
||||
(workingDir / "_shredder").deleteExisting()
|
||||
(workingDir / "_teststore").deleteExisting()
|
||||
return workingDir.resolve(jarName) to signer
|
||||
}
|
||||
}
|
@ -3,7 +3,8 @@ package net.corda.testing.core.internal
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.JarSignatureCollector
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.coretesting.internal.modifyJarManifest
|
||||
import net.corda.coretesting.internal.useZipFile
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import java.io.Closeable
|
||||
import java.io.FileInputStream
|
||||
@ -18,6 +19,10 @@ import java.util.jar.Attributes
|
||||
import java.util.jar.JarInputStream
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
import kotlin.io.path.deleteExisting
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
@ -31,12 +36,11 @@ class SelfCleaningDir : Closeable {
|
||||
}
|
||||
|
||||
object JarSignatureTestUtils {
|
||||
val bin = Paths.get(System.getProperty("java.home")).let { if (it.endsWith("jre")) it.parent else it } / "bin"
|
||||
private val bin = Paths.get(System.getProperty("java.home")).let { if (it.endsWith("jre")) it.parent else it } / "bin"
|
||||
|
||||
fun Path.executeProcess(vararg command: String) {
|
||||
private fun Path.executeProcess(vararg command: String) {
|
||||
val shredder = (this / "_shredder").toFile() // No need to delete after each test.
|
||||
assertEquals(0, ProcessBuilder()
|
||||
.inheritIO()
|
||||
.redirectOutput(shredder)
|
||||
.redirectError(shredder)
|
||||
.directory(this.toFile())
|
||||
@ -45,7 +49,7 @@ object JarSignatureTestUtils {
|
||||
.waitFor())
|
||||
}
|
||||
|
||||
val CODE_SIGNER = CordaX500Name("Test Code Signing Service", "London", "GB")
|
||||
private val CODE_SIGNER = CordaX500Name("Test Code Signing Service", "London", "GB")
|
||||
|
||||
fun Path.generateKey(alias: String = "Test", storePassword: String = "secret!", name: String = CODE_SIGNER.toString(), keyalg: String = "RSA", keyPassword: String = storePassword, storeName: String = "_teststore") : PublicKey {
|
||||
executeProcess("keytool", "-genkeypair", "-keystore", storeName, "-storepass", storePassword, "-keyalg", keyalg, "-alias", alias, "-keypass", keyPassword, "-dname", name)
|
||||
@ -69,6 +73,17 @@ object JarSignatureTestUtils {
|
||||
return ks.getCertificate(alias).publicKey
|
||||
}
|
||||
|
||||
fun Path.unsignJar() {
|
||||
// Remove the signatures
|
||||
useZipFile { zipFs ->
|
||||
zipFs.getPath("META-INF").takeIf { it.exists() }?.listDirectoryEntries("*.{SF,DSA,RSA,EC}")?.forEach(Path::deleteExisting)
|
||||
}
|
||||
// Remove all the hash information of the jar contents
|
||||
modifyJarManifest { manifest ->
|
||||
manifest.entries.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun Path.getPublicKey(alias: String, storeName: String, storePassword: String) : PublicKey {
|
||||
val ks = loadKeyStore(this.resolve(storeName), storePassword)
|
||||
return ks.getCertificate(alias).publicKey
|
||||
|
@ -2,7 +2,7 @@ package net.corda.coretesting.internal
|
||||
|
||||
import org.assertj.core.api.Assertions.catchThrowable
|
||||
import org.hamcrest.Matchers.isA
|
||||
import org.junit.Assert.assertThat
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.Test
|
||||
import java.io.Closeable
|
||||
import java.io.InputStream
|
||||
@ -44,7 +44,6 @@ class RigorousMockTest {
|
||||
fun `callRealMethod is preferred by rigorousMock`() {
|
||||
rigorousMock<MyInterface>().let { m ->
|
||||
assertSame<Any>(UndefinedMockBehaviorException::class.java, catchThrowable { m.abstractFun() }.javaClass)
|
||||
assertSame<Any>(UndefinedMockBehaviorException::class.java, catchThrowable { m.kotlinDefaultFun() }.javaClass)
|
||||
}
|
||||
rigorousMock<MyAbstract>().let { m ->
|
||||
assertSame<Any>(UndefinedMockBehaviorException::class.java, catchThrowable { m.abstractFun() }.javaClass)
|
||||
|
@ -1,14 +1,15 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'kotlin-jpa'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.jpa'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda Node Driver module'
|
||||
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
integrationTestImplementation.extendsFrom testImplementation
|
||||
integrationTestRuntime.extendsFrom testRuntimeOnly
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -25,14 +26,33 @@ sourceSets {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':test-utils')
|
||||
implementation project(':core')
|
||||
implementation project(':node')
|
||||
implementation project(':node-api')
|
||||
implementation project(':serialization')
|
||||
implementation project(':client:rpc')
|
||||
implementation project(':client:mock')
|
||||
implementation project(':common-configuration-parsing')
|
||||
implementation project(':common-validation')
|
||||
implementation project(':core-test-utils')
|
||||
implementation project(':test-common')
|
||||
implementation project(':test-utils')
|
||||
implementation project(':tools:cliutils')
|
||||
|
||||
compile group: 'org.apache.sshd', name: 'sshd-common', version: '2.9.2'
|
||||
implementation group: 'org.apache.sshd', name: 'sshd-common', version: '2.12.1'
|
||||
implementation "javax.persistence:javax.persistence-api:2.2"
|
||||
|
||||
// Integration test helpers
|
||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||
testImplementation "org.assertj:assertj-core:$assertj_version"
|
||||
|
||||
integrationTestImplementation project(":client:jackson")
|
||||
integrationTestImplementation "junit:junit:$junit_version"
|
||||
integrationTestImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
integrationTestImplementation "info.picocli:picocli:$picocli_version"
|
||||
integrationTestImplementation "com.google.guava:guava:$guava_version"
|
||||
integrationTestImplementation 'com.googlecode.json-simple:json-simple:1.1.1'
|
||||
integrationTestImplementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
integrationTestImplementation 'org.hamcrest:hamcrest-library:2.1'
|
||||
|
||||
integrationTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
integrationTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
@ -40,24 +60,60 @@ dependencies {
|
||||
|
||||
// Jetty dependencies for NetworkMapClient test.
|
||||
// Web stuff: for HTTP[S] servlets
|
||||
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
|
||||
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
|
||||
compile "javax.servlet:javax.servlet-api:${servlet_version}"
|
||||
implementation "org.eclipse.jetty.ee10:jetty-ee10-servlet:${jetty_version}"
|
||||
implementation "org.eclipse.jetty.ee10:jetty-ee10-webapp:${jetty_version}"
|
||||
implementation "javax.servlet:javax.servlet-api:${servlet_version}"
|
||||
|
||||
implementation "org.gradle:gradle-tooling-api:7.1"
|
||||
|
||||
compile "org.gradle:gradle-tooling-api:${gradle.gradleVersion}"
|
||||
|
||||
// Jersey for JAX-RS implementation for use in Jetty
|
||||
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
|
||||
compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
|
||||
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
|
||||
implementation "org.glassfish.jersey.core:jersey-server:${jersey_version}"
|
||||
implementation "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
|
||||
implementation "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
|
||||
implementation "org.glassfish.jersey.inject:jersey-hk2:$jersey_version"
|
||||
|
||||
implementation "io.reactivex:rxjava:$rxjava_version"
|
||||
implementation("org.apache.activemq:artemis-core-client:${artemis_version}") {
|
||||
exclude group: 'org.jgroups', module: 'jgroups'
|
||||
}
|
||||
|
||||
// Bouncy castle support needed for X509 certificate manipulation
|
||||
implementation "org.bouncycastle:bcprov-lts8on:${bouncycastle_version}"
|
||||
implementation "org.bouncycastle:bcpkix-lts8on:${bouncycastle_version}"
|
||||
implementation "org.bouncycastle:bcutil-lts8on:${bouncycastle_version}"
|
||||
|
||||
implementation "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||
implementation "com.google.jimfs:jimfs:1.1"
|
||||
implementation group: "com.typesafe", name: "config", version: typesafe_config_version
|
||||
implementation "io.github.classgraph:classgraph:$class_graph_version"
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
|
||||
implementation "com.esotericsoftware:kryo:$kryo_version"
|
||||
implementation "io.dropwizard.metrics:metrics-jmx:$metrics_version"
|
||||
implementation "org.apache.commons:commons-lang3:$commons_lang3_version"
|
||||
implementation "org.assertj:assertj-core:${assertj_version}"
|
||||
implementation "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
implementation "junit:junit:$junit_version"
|
||||
|
||||
implementation("org.apache.activemq:artemis-server:${artemis_version}") {
|
||||
exclude group: 'org.apache.commons', module: 'commons-dbcp2'
|
||||
exclude group: 'org.jgroups', module: 'jgroups'
|
||||
}
|
||||
|
||||
implementation "co.paralleluniverse:quasar-core:$quasar_version"
|
||||
}
|
||||
|
||||
compileJava {
|
||||
doFirst {
|
||||
if (JavaVersion.current() == JavaVersion.VERSION_11)
|
||||
options.compilerArgs = [
|
||||
'--add-exports', 'java.base/sun.nio.ch=ALL-UNNAMED'
|
||||
]
|
||||
options.compilerArgs = [
|
||||
'--add-modules', 'jdk.incubator.foreign'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
from(project(":node:capsule").files("src/main/resources/node-jvm-args.txt")) {
|
||||
into("net/corda/testing/node/internal")
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,20 +131,10 @@ jar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tasks.named('javadocJar', Jar) {
|
||||
from 'README.md'
|
||||
include 'README.md'
|
||||
}
|
||||
|
||||
tasks.named('javadoc', Javadoc) {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
}
|
||||
|
||||
scanApi {
|
||||
//Constructors that are synthesized by Kotlin unexpectedly
|
||||
excludeMethods = [
|
||||
@ -99,4 +145,13 @@ scanApi {
|
||||
"<init>(Lnet/corda/testing/node/InMemoryMessagingNetwork\$PeerHandle;Lnet/corda/node/services/messaging/Message;Lnet/corda/core/messaging/MessageRecipients;Lkotlin/jvm/internal/DefaultConstructorMarker;)V"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId jar.baseName
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,6 @@ import net.corda.core.internal.CertRole
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.isRegularFile
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.readLines
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||
@ -26,12 +22,16 @@ import org.assertj.core.api.Assertions.assertThatCode
|
||||
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
import org.json.simple.JSONObject
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import java.util.LinkedList
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import kotlin.streams.toList
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.useDirectoryEntries
|
||||
import kotlin.io.path.useLines
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DriverTests {
|
||||
@ -99,8 +99,8 @@ class DriverTests {
|
||||
systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString())
|
||||
)) {
|
||||
val baseDirectory = startNode(providedName = DUMMY_BANK_A_NAME).getOrThrow().baseDirectory
|
||||
val logFile = (baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list { it.filter { a -> a.isRegularFile() && a.fileName.toString().startsWith("node") }.findFirst().get() }
|
||||
val debugLinesPresent = logFile.readLines { lines -> lines.anyMatch { line -> line.startsWith("[DEBUG]") } }
|
||||
val logFile = (baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).useDirectoryEntries { it.single { a -> a.isRegularFile() && a.name.startsWith("node") } }
|
||||
val debugLinesPresent = logFile.useLines { lines -> lines.any { line -> line.startsWith("[DEBUG]") } }
|
||||
assertThat(debugLinesPresent).isTrue()
|
||||
}
|
||||
}
|
||||
@ -185,5 +185,5 @@ class DriverTests {
|
||||
testFuture.getOrThrow()
|
||||
}
|
||||
|
||||
private fun DriverDSL.newNode(name: CordaX500Name) = { startNode(NodeParameters(providedName = name)) }
|
||||
}
|
||||
private fun DriverDSL.newNode(name: CordaX500Name): () -> CordaFuture<NodeHandle> = { startNode(NodeParameters(providedName = name)) }
|
||||
}
|
||||
|
@ -3,9 +3,13 @@ package net.corda.testing.node
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.FlowStackSnapshot
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.internal.read
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -16,6 +20,8 @@ import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.nio.file.Path
|
||||
import java.time.LocalDate
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.useDirectoryEntries
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
@ -29,11 +35,11 @@ data class StackSnapshotFrame(val method: String, val clazz: String, val dataTyp
|
||||
* an empty list the frame is considered to be full.
|
||||
*/
|
||||
fun convertToStackSnapshotFrames(snapshot: FlowStackSnapshot): List<StackSnapshotFrame> {
|
||||
return snapshot.stackFrames.map {
|
||||
val dataTypes = it.stackObjects.map {
|
||||
return snapshot.stackFrames.map { frame ->
|
||||
val dataTypes = frame.stackObjects.map {
|
||||
if (it == null) null else it::class.qualifiedName
|
||||
}
|
||||
val stackTraceElement = it.stackTraceElement
|
||||
val stackTraceElement = frame.stackTraceElement
|
||||
StackSnapshotFrame(stackTraceElement.methodName, stackTraceElement.className, dataTypes)
|
||||
}
|
||||
}
|
||||
@ -48,7 +54,7 @@ fun convertToStackSnapshotFrames(snapshot: FlowStackSnapshot): List<StackSnapsho
|
||||
*/
|
||||
@StartableByRPC
|
||||
class SideEffectFlow : FlowLogic<List<StackSnapshotFrame>>() {
|
||||
var sideEffectField = ""
|
||||
private var sideEffectField = ""
|
||||
|
||||
@Suspendable
|
||||
override fun call(): List<StackSnapshotFrame> {
|
||||
@ -155,7 +161,7 @@ class PersistingSideEffectFlow : FlowLogic<StateMachineRunId>() {
|
||||
* Similar to [PersistingSideEffectFlow] but aims to produce multiple snapshot files.
|
||||
*/
|
||||
@StartableByRPC
|
||||
class MultiplePersistingSideEffectFlow(val persistCallCount: Int) : FlowLogic<StateMachineRunId>() {
|
||||
class MultiplePersistingSideEffectFlow(private val persistCallCount: Int) : FlowLogic<StateMachineRunId>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): StateMachineRunId {
|
||||
@ -212,7 +218,7 @@ private fun flowSnapshotDir(baseDir: Path, flowId: StateMachineRunId): Path {
|
||||
}
|
||||
|
||||
fun countFilesInDir(baseDir: Path, flowId: StateMachineRunId): Int {
|
||||
return flowSnapshotDir(baseDir, flowId).list { it.count().toInt() }
|
||||
return flowSnapshotDir(baseDir, flowId).useDirectoryEntries { it.count() }
|
||||
}
|
||||
|
||||
fun assertFrame(expectedMethod: String, expectedEmpty: Boolean, frame: StackSnapshotFrame) {
|
||||
@ -311,9 +317,9 @@ class FlowStackSnapshotTest {
|
||||
val snapshotFromFile = readFlowStackSnapshotFromDir(a.baseDirectory, flowId)
|
||||
var inCallCount = 0
|
||||
var inPersistCount = 0
|
||||
snapshotFromFile.stackFrames.forEach {
|
||||
val trace = it.stackTraceElement
|
||||
it.stackObjects.forEach {
|
||||
snapshotFromFile.stackFrames.forEach { frame ->
|
||||
val trace = frame.stackTraceElement
|
||||
frame.stackObjects.forEach {
|
||||
when (it) {
|
||||
Constants.IN_CALL_VALUE -> {
|
||||
assertEquals(PersistingSideEffectFlow::call.name, trace.methodName)
|
||||
|
@ -1,10 +1,14 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
|
||||
import net.corda.testing.node.internal.nodeJvmArgs
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
|
||||
class MockNetworkIntegrationTests {
|
||||
companion object {
|
||||
@ -21,6 +25,17 @@ class MockNetworkIntegrationTests {
|
||||
@Test(timeout=300_000)
|
||||
fun `does not leak non-daemon threads`() {
|
||||
val quasar = projectRootDir / "lib" / "quasar.jar"
|
||||
assertEquals(0, startJavaProcess<MockNetworkIntegrationTests>(emptyList(), extraJvmArguments = listOf("-javaagent:$quasar")).waitFor())
|
||||
val quasarOptions = "m"
|
||||
|
||||
val workingDirectory = Path("build", "MockNetworkIntegrationTests").apply {
|
||||
deleteRecursively()
|
||||
createDirectories()
|
||||
}
|
||||
val process = startJavaProcess<MockNetworkIntegrationTests>(
|
||||
emptyList(),
|
||||
workingDirectory = workingDirectory,
|
||||
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
|
||||
)
|
||||
assertThat(process.waitFor()).isZero()
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.testing.internal.IS_OPENJ9
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.matchesPattern
|
||||
import org.junit.Assume
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
@ -11,7 +9,6 @@ import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.stream.Collectors
|
||||
|
||||
|
||||
@RunWith(value = Parameterized::class)
|
||||
class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputRegexPattern: String) {
|
||||
|
||||
@ -33,22 +30,19 @@ class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputR
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Run CordaCliWrapper sample app with arguments and check error output matches regExp`() {
|
||||
// For openj9 the process error output appears sometimes to be garbled.
|
||||
Assume.assumeTrue(!IS_OPENJ9)
|
||||
val process = ProcessUtilities.startJavaProcess(
|
||||
className = className,
|
||||
arguments = arguments,
|
||||
inheritIO = false)
|
||||
val process = ProcessUtilities.startJavaProcess(className = className, arguments = arguments)
|
||||
|
||||
process.waitFor()
|
||||
|
||||
val processErrorOutput = BufferedReader(
|
||||
InputStreamReader(process.errorStream))
|
||||
.lines()
|
||||
.filter { !it.startsWith("Warning: Nashorn") }
|
||||
.filter { it.contains("Exception") ||
|
||||
it.contains("at ") ||
|
||||
it.contains("exception") }
|
||||
.collect(Collectors.joining("\n"))
|
||||
.toString()
|
||||
|
||||
assertThat(processErrorOutput, matchesPattern(outputRegexPattern))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
|
||||
class InternalMockNetworkIntegrationTests {
|
||||
companion object {
|
||||
@ -21,6 +24,17 @@ class InternalMockNetworkIntegrationTests {
|
||||
@Test(timeout=300_000)
|
||||
fun `does not leak non-daemon threads`() {
|
||||
val quasar = projectRootDir / "lib" / "quasar.jar"
|
||||
assertEquals(0, startJavaProcess<InternalMockNetworkIntegrationTests>(emptyList(), extraJvmArguments = listOf("-javaagent:$quasar")).waitFor())
|
||||
val quasarOptions = "m"
|
||||
|
||||
val workingDirectory = Path("build", "InternalMockNetworkIntegrationTests").apply {
|
||||
deleteRecursively()
|
||||
createDirectories()
|
||||
}
|
||||
val process = startJavaProcess<InternalMockNetworkIntegrationTests>(
|
||||
emptyList(),
|
||||
workingDirectory = workingDirectory,
|
||||
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
|
||||
)
|
||||
assertThat(process.waitFor()).isZero()
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.readText
|
||||
import net.corda.core.internal.writeText
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.io.path.writeText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
@ -1,97 +1,108 @@
|
||||
package net.corda.testing.driver;
|
||||
|
||||
import sun.misc.Unsafe;
|
||||
import sun.nio.ch.DirectBuffer;
|
||||
import jdk.incubator.foreign.MemoryHandles;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.lang.reflect.Field;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler):
|
||||
* import sun.misc.Unsafe;
|
||||
* import sun.nio.ch.DirectBuffer;
|
||||
*/
|
||||
import static java.nio.file.StandardOpenOption.READ;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
// This was originally (re)written in Java to access internal JDK APIs. Since it's no longer doing that, this can be converted back to Kotlin.
|
||||
public class SharedMemoryIncremental extends PortAllocation {
|
||||
private static final int DEFAULT_START_PORT = 10_000;
|
||||
private static final int FIRST_EPHEMERAL_PORT = 30_000;
|
||||
|
||||
static private final int DEFAULT_START_PORT = 10_000;
|
||||
static private final int FIRST_EPHEMERAL_PORT = 30_000;
|
||||
private final int startPort;
|
||||
private final int endPort;
|
||||
|
||||
private int startPort;
|
||||
private int endPort;
|
||||
|
||||
private MappedByteBuffer mb;
|
||||
private Long startingAddress;
|
||||
|
||||
private File file = new File(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
|
||||
private RandomAccessFile backingFile;
|
||||
{
|
||||
try {
|
||||
backingFile = new RandomAccessFile(file, "rw");
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
private final MemorySegment memorySegment;
|
||||
private final VarHandle intHandle;
|
||||
private final MappedByteBuffer unsafeBuffer;
|
||||
|
||||
private SharedMemoryIncremental(int startPort, int endPort) {
|
||||
this.startPort = startPort;
|
||||
this.endPort = endPort;
|
||||
Path file = Path.of(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
|
||||
try {
|
||||
mb = backingFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 16);
|
||||
startingAddress = ((DirectBuffer) mb).address();
|
||||
try {
|
||||
Files.createFile(file);
|
||||
} catch (FileAlreadyExistsException ignored) {}
|
||||
if (isFfmAvailable()) {
|
||||
memorySegment = MemorySegment.mapFile(file, 0, Integer.SIZE, MapMode.READ_WRITE, ResourceScope.globalScope());
|
||||
intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
|
||||
unsafeBuffer = null;
|
||||
} else {
|
||||
LoggerFactory.getLogger(getClass()).warn("Using unsafe port allocator which may lead to the same port being allocated " +
|
||||
"twice. Consider adding --add-modules=jdk.incubator.foreign to the test JVM.");
|
||||
memorySegment = null;
|
||||
intHandle = null;
|
||||
unsafeBuffer = FileChannel.open(file, READ, WRITE).map(MapMode.READ_WRITE, 0, Integer.SIZE);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFfmAvailable() {
|
||||
try {
|
||||
Class.forName("jdk.incubator.foreign.MemorySegment");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static SharedMemoryIncremental INSTANCE = new SharedMemoryIncremental(DEFAULT_START_PORT, FIRST_EPHEMERAL_PORT);
|
||||
static private Unsafe UNSAFE = getUnsafe();
|
||||
|
||||
static private Unsafe getUnsafe() {
|
||||
try {
|
||||
Field f = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
f.setAccessible(true);
|
||||
return (Unsafe) f.get(null);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextPort() {
|
||||
long oldValue;
|
||||
long newValue;
|
||||
boolean loopSuccess;
|
||||
do {
|
||||
oldValue = UNSAFE.getLongVolatile(null, startingAddress);
|
||||
while (true) {
|
||||
int oldValue;
|
||||
if (intHandle != null) {
|
||||
oldValue = (int) intHandle.getVolatile(memorySegment, 0L);
|
||||
} else {
|
||||
oldValue = unsafeBuffer.getInt(0);
|
||||
}
|
||||
int newValue;
|
||||
if (oldValue + 1 >= endPort || oldValue < startPort) {
|
||||
newValue = startPort;
|
||||
} else {
|
||||
newValue = (oldValue + 1);
|
||||
}
|
||||
boolean reserveSuccess = UNSAFE.compareAndSwapLong(null, startingAddress, oldValue, newValue);
|
||||
loopSuccess = reserveSuccess && isLocalPortAvailable(newValue);
|
||||
} while (!loopSuccess);
|
||||
|
||||
return (int) newValue;
|
||||
if (intHandle != null) {
|
||||
if (!intHandle.compareAndSet(memorySegment, 0L, oldValue, newValue)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
unsafeBuffer.putInt(0, newValue);
|
||||
}
|
||||
if (isLocalPortAvailable(newValue)) {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isLocalPortAvailable(Long portToTest) {
|
||||
try (ServerSocket serverSocket = new ServerSocket(Math.toIntExact(portToTest))) {
|
||||
private boolean isLocalPortAvailable(int portToTest) {
|
||||
try (ServerSocket ignored = new ServerSocket(portToTest)) {
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// Don't catch anything other than IOException here in case we
|
||||
// accidentally create an infinite loop. For example, installing
|
||||
// a SecurityManager could throw AccessControlException.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import net.corda.core.DoNotImplement
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.node.NetworkParameters
|
||||
@ -31,6 +30,7 @@ import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.io.path.div
|
||||
|
||||
/**
|
||||
* Object ecapsulating a notary started automatically by the driver.
|
||||
@ -98,7 +98,6 @@ interface InProcess : NodeHandle {
|
||||
|
||||
/**
|
||||
* Starts an already constructed flow. Note that you must be on the server thread to call this method.
|
||||
* @param context indicates who started the flow, see: [InvocationContext].
|
||||
*/
|
||||
fun <T> startFlow(logic: FlowLogic<T>): CordaFuture<T> = internalServices.startFlow(logic, internalServices.newContext())
|
||||
.getOrThrow().resultFuture
|
||||
@ -628,7 +627,7 @@ data class DriverParameters(
|
||||
waitForAllNodesToFinish: Boolean,
|
||||
notarySpecs: List<NotarySpec>,
|
||||
extraCordappPackagesToScan: List<String>,
|
||||
@Suppress("DEPRECATION") jmxPolicy: JmxPolicy,
|
||||
jmxPolicy: JmxPolicy,
|
||||
networkParameters: NetworkParameters,
|
||||
notaryCustomOverrides: Map<String, Any?>,
|
||||
inMemoryDB: Boolean,
|
||||
|
@ -25,7 +25,7 @@ import net.corda.testing.node.User
|
||||
* log level argument.
|
||||
* @property rpcAddress optional override for RPC address on which node will be accepting RPC connections from the clients. Port provided must be vacant.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@Suppress("unused", "TooManyFunctions")
|
||||
data class NodeParameters(
|
||||
val providedName: CordaX500Name? = null,
|
||||
val rpcUsers: List<User> = emptyList(),
|
||||
@ -37,7 +37,8 @@ data class NodeParameters(
|
||||
val flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>> = emptyMap(),
|
||||
val logLevelOverride: String? = null,
|
||||
val rpcAddress: NetworkHostAndPort? = null,
|
||||
val systemProperties: Map<String, String> = emptyMap()
|
||||
val systemProperties: Map<String, String> = emptyMap(),
|
||||
val legacyContracts: Collection<TestCordapp> = emptySet()
|
||||
) {
|
||||
/**
|
||||
* Create a new node parameters object with default values. Each parameter can be specified with its wither method which returns a copy
|
||||
@ -54,6 +55,9 @@ data class NodeParameters(
|
||||
fun withAdditionalCordapps(additionalCordapps: Set<TestCordapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
|
||||
fun withFlowOverrides(flowOverrides: Map<Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>): NodeParameters = copy(flowOverrides = flowOverrides)
|
||||
fun withLogLevelOverride(logLevelOverride: String?): NodeParameters = copy(logLevelOverride = logLevelOverride)
|
||||
fun withRpcAddress(rpcAddress: NetworkHostAndPort?): NodeParameters = copy(rpcAddress = rpcAddress)
|
||||
fun withSystemProperties(systemProperties: Map<String, String>): NodeParameters = copy(systemProperties = systemProperties)
|
||||
fun withLegacyContracts(legacyContracts: Collection<TestCordapp>): NodeParameters = copy(legacyContracts = legacyContracts)
|
||||
|
||||
constructor(
|
||||
providedName: CordaX500Name?,
|
||||
@ -221,4 +225,58 @@ data class NodeParameters(
|
||||
logLevelOverride = logLevelOverride,
|
||||
rpcAddress = rpcAddress,
|
||||
systemProperties = systemProperties)
|
||||
|
||||
constructor(
|
||||
providedName: CordaX500Name?,
|
||||
rpcUsers: List<User>,
|
||||
verifierType: VerifierType,
|
||||
customOverrides: Map<String, Any?>,
|
||||
startInSameProcess: Boolean?,
|
||||
maximumHeapSize: String,
|
||||
additionalCordapps: Collection<TestCordapp> = emptySet(),
|
||||
flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>,
|
||||
logLevelOverride: String? = null,
|
||||
rpcAddress: NetworkHostAndPort? = null,
|
||||
systemProperties: Map<String, String> = emptyMap()
|
||||
) : this(
|
||||
providedName,
|
||||
rpcUsers,
|
||||
verifierType,
|
||||
customOverrides,
|
||||
startInSameProcess,
|
||||
maximumHeapSize,
|
||||
additionalCordapps,
|
||||
flowOverrides,
|
||||
logLevelOverride,
|
||||
rpcAddress,
|
||||
systemProperties,
|
||||
legacyContracts = emptySet())
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun copy(
|
||||
providedName: CordaX500Name?,
|
||||
rpcUsers: List<User>,
|
||||
verifierType: VerifierType,
|
||||
customOverrides: Map<String, Any?>,
|
||||
startInSameProcess: Boolean?,
|
||||
maximumHeapSize: String,
|
||||
additionalCordapps: Collection<TestCordapp> = emptySet(),
|
||||
flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>,
|
||||
logLevelOverride: String? = null,
|
||||
rpcAddress: NetworkHostAndPort? = null,
|
||||
systemProperties: Map<String, String> = emptyMap()
|
||||
) = this.copy(
|
||||
providedName = providedName,
|
||||
rpcUsers = rpcUsers,
|
||||
verifierType = verifierType,
|
||||
customOverrides = customOverrides,
|
||||
startInSameProcess = startInSameProcess,
|
||||
maximumHeapSize = maximumHeapSize,
|
||||
additionalCordapps = additionalCordapps,
|
||||
flowOverrides = flowOverrides,
|
||||
logLevelOverride = logLevelOverride,
|
||||
rpcAddress = rpcAddress,
|
||||
systemProperties = systemProperties,
|
||||
legacyContracts = legacyContracts)
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.testing.driver.internal
|
||||
|
||||
import jakarta.validation.constraints.NotNull
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.node.NodeInfo
|
||||
@ -14,7 +15,6 @@ import net.corda.testing.driver.OutOfProcess
|
||||
import net.corda.testing.node.User
|
||||
import rx.Observable
|
||||
import java.nio.file.Path
|
||||
import javax.validation.constraints.NotNull
|
||||
|
||||
interface NodeHandleInternal : NodeHandle {
|
||||
val configuration: NodeConfiguration
|
||||
|
@ -1,21 +1,31 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
import com.google.common.collect.MutableClassToInstanceMap
|
||||
import net.corda.core.CordaInternal
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.cordapp.CordappProviderInternal
|
||||
import net.corda.core.internal.getRequiredTransaction
|
||||
import net.corda.core.internal.mapToSet
|
||||
import net.corda.core.internal.requireSupportedHashType
|
||||
import net.corda.core.internal.telemetry.TelemetryComponent
|
||||
import net.corda.core.internal.telemetry.TelemetryServiceImpl
|
||||
import net.corda.core.internal.verification.ExternalVerifierHandle
|
||||
import net.corda.core.internal.verification.VerifyingServiceHub
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
@ -34,33 +44,34 @@ import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.NetworkParametersService
|
||||
import net.corda.core.node.services.ServiceLifecycleObserver
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
import net.corda.core.node.services.TransactionVerifierService
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.diagnostics.DiagnosticsService
|
||||
import net.corda.core.node.services.vault.CordaTransactionSupport
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.coretesting.internal.DEV_ROOT_CA
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.ServicesForResolutionImpl
|
||||
import net.corda.node.internal.NodeServicesForResolution
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
|
||||
import net.corda.node.services.api.VaultServiceInternal
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
|
||||
import net.corda.node.services.diagnostics.NodeDiagnosticsService
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
import net.corda.node.services.keys.BasicHSMKeyManagementService
|
||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
||||
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
|
||||
import net.corda.node.services.persistence.toInternal
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.nodeapi.internal.cordapp.CordappLoader
|
||||
import net.corda.nodeapi.internal.cordapp.cordappSchemas
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.nodeapi.internal.persistence.contextTransaction
|
||||
@ -69,7 +80,6 @@ import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.MockCordappProvider
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.testing.internal.configureDatabase
|
||||
import net.corda.testing.node.internal.DriverDSLImpl
|
||||
import net.corda.testing.node.internal.MockCryptoService
|
||||
import net.corda.testing.node.internal.MockKeyManagementService
|
||||
import net.corda.testing.node.internal.MockNetworkParametersStorage
|
||||
@ -78,6 +88,7 @@ import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.getCallerPackage
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
import java.sql.Connection
|
||||
@ -116,10 +127,9 @@ open class MockServices private constructor(
|
||||
*arrayOf(initialIdentity.keyPair) + moreKeys
|
||||
)
|
||||
) : ServiceHub {
|
||||
|
||||
companion object {
|
||||
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
|
||||
return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).map { it.jarFile.toUri().toURL() }, versionInfo)
|
||||
return JarScanningCordappLoader(cordappsForPackages(packages).mapToSet { it.jarFile }, versionInfo = versionInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,8 +145,8 @@ open class MockServices private constructor(
|
||||
val dbPath = dbDir.resolve("persistence")
|
||||
try {
|
||||
DatabaseSnapshot.copyDatabaseSnapshot(dbDir)
|
||||
} catch (ex: java.nio.file.FileAlreadyExistsException) {
|
||||
DriverDSLImpl.log.warn("Database already exists on disk, not attempting to pre-migrate database.")
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
loggerFor<MockServices>().warn("Database already exists on disk, not attempting to pre-migrate database.")
|
||||
}
|
||||
val props = Properties()
|
||||
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
|
||||
@ -295,22 +305,19 @@ open class MockServices private constructor(
|
||||
// Because Kotlin is dumb and makes not publicly visible objects public, thus changing the public API.
|
||||
private val mockStateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()
|
||||
|
||||
private val dummyAttachment by lazy {
|
||||
val inputStream = ByteArrayOutputStream().apply {
|
||||
ZipOutputStream(this).use {
|
||||
with(it) {
|
||||
putNextEntry(ZipEntry(JarFile.MANIFEST_NAME))
|
||||
}
|
||||
}
|
||||
}.toByteArray().inputStream()
|
||||
val attachment = object : Attachment {
|
||||
override val id get() = throw UnsupportedOperationException()
|
||||
override fun open() = inputStream
|
||||
override val signerKeys get() = throw UnsupportedOperationException()
|
||||
override val signers: List<Party> get() = throw UnsupportedOperationException()
|
||||
override val size: Int = 512
|
||||
private val dummyAttachment: Attachment by lazy {
|
||||
object : AbstractAttachment(
|
||||
{
|
||||
val baos = ByteArrayOutputStream()
|
||||
ZipOutputStream(baos).use { zip ->
|
||||
zip.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME))
|
||||
}
|
||||
baos.toByteArray()
|
||||
},
|
||||
null
|
||||
) {
|
||||
override val id: SecureHash by lazy(attachmentData::sha256)
|
||||
}
|
||||
attachment
|
||||
}
|
||||
}
|
||||
|
||||
@ -482,27 +489,23 @@ open class MockServices private constructor(
|
||||
get() {
|
||||
return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L)
|
||||
}
|
||||
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments).also {
|
||||
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments.toInternal()).also {
|
||||
it.start()
|
||||
}
|
||||
override val transactionVerifierService: TransactionVerifierService
|
||||
get() = InMemoryTransactionVerifierService(
|
||||
numberOfWorkers = 2,
|
||||
cordappProvider = mockCordappProvider,
|
||||
attachments = attachments
|
||||
)
|
||||
override val cordappProvider: CordappProvider get() = mockCordappProvider
|
||||
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(initialNetworkParameters)
|
||||
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
|
||||
|
||||
protected val servicesForResolution: ServicesForResolution
|
||||
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions)
|
||||
// This is kept here for backwards compatibility, otherwise this has no extra utility.
|
||||
protected val servicesForResolution: ServicesForResolution get() = verifyingView
|
||||
|
||||
private val verifyingView: VerifyingServiceHub by lazy { VerifyingView(this) }
|
||||
|
||||
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
|
||||
return NodeVaultService(
|
||||
clock,
|
||||
keyManagementService,
|
||||
servicesForResolution as NodeServicesForResolution,
|
||||
verifyingView,
|
||||
database,
|
||||
schemaService,
|
||||
cordappLoader.appClassLoader
|
||||
@ -511,9 +514,9 @@ open class MockServices private constructor(
|
||||
|
||||
// This needs to be internal as MutableClassToInstanceMap is a guava type and shouldn't be part of our public API
|
||||
/** A map of available [CordaService] implementations */
|
||||
internal val cordappServices: MutableClassToInstanceMap<SerializeAsToken> = MutableClassToInstanceMap.create<SerializeAsToken>()
|
||||
internal val cordappServices: MutableClassToInstanceMap<SerializeAsToken> = MutableClassToInstanceMap.create()
|
||||
|
||||
internal val cordappTelemetryComponents: MutableClassToInstanceMap<TelemetryComponent> = MutableClassToInstanceMap.create<TelemetryComponent>()
|
||||
private val cordappTelemetryComponents: MutableClassToInstanceMap<TelemetryComponent> = MutableClassToInstanceMap.create()
|
||||
|
||||
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
|
||||
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
|
||||
@ -543,19 +546,46 @@ open class MockServices private constructor(
|
||||
mockCordappProvider.addMockCordapp(contractClassName, attachments)
|
||||
}
|
||||
|
||||
override fun loadState(stateRef: StateRef) = servicesForResolution.loadState(stateRef)
|
||||
override fun loadStates(stateRefs: Set<StateRef>) = servicesForResolution.loadStates(stateRefs)
|
||||
override fun loadState(stateRef: StateRef): TransactionState<ContractState> {
|
||||
return getRequiredTransaction(stateRef.txhash).resolveBaseTransaction(this).outputs[stateRef.index]
|
||||
}
|
||||
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = stateRefs.mapToSet(::toStateAndRef)
|
||||
|
||||
/** Returns a dummy Attachment, in context of signature constrains non-downgrade rule this default to contract class version `1`. */
|
||||
override fun loadContractAttachment(stateRef: StateRef) = dummyAttachment
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle.
|
||||
*/
|
||||
fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
|
||||
class MockAppServiceHubImpl<out T : SerializeAsToken>(val serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) : AppServiceHub, ServiceHub by serviceHub {
|
||||
val serviceInstance: T = serviceConstructor(this)
|
||||
|
||||
/**
|
||||
* All [ServiceHub]s must also implement [VerifyingServiceHub]. However, since [MockServices] is part of the public API, making it
|
||||
* extend [VerifyingServiceHub] would leak internal APIs. Instead we have this private view class and have the `toVerifyingServiceHub`
|
||||
* extension method return it.
|
||||
*/
|
||||
private class VerifyingView(private val mockServices: MockServices) : VerifyingServiceHub, ServiceHub by mockServices {
|
||||
override val attachmentTrustCalculator = NodeAttachmentTrustCalculator(
|
||||
attachmentStorage = mockServices.attachments.toInternal(),
|
||||
cacheFactory = TestingNamedCacheFactory()
|
||||
)
|
||||
|
||||
override val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
|
||||
|
||||
override val cordappProvider: CordappProviderInternal get() = mockServices.mockCordappProvider
|
||||
|
||||
override fun loadContractAttachment(stateRef: StateRef): Attachment = mockServices.loadContractAttachment(stateRef)
|
||||
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> = mockServices.loadState(stateRef)
|
||||
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = mockServices.loadStates(stateRefs)
|
||||
|
||||
override val externalVerifierHandle: ExternalVerifierHandle
|
||||
get() = throw UnsupportedOperationException("`Verification of legacy transactions is not supported by MockServices. Use MockNode instead.")
|
||||
}
|
||||
|
||||
|
||||
@CordaInternal
|
||||
internal class MockAppServiceHubImpl<out T : SerializeAsToken>(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) :
|
||||
AppServiceHub, VerifyingServiceHub by serviceHub.verifyingView {
|
||||
internal val serviceInstance: T = serviceConstructor(this)
|
||||
|
||||
init {
|
||||
serviceHub.cordappServices.putInstance(serviceInstance.javaClass, serviceInstance)
|
||||
@ -576,5 +606,11 @@ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serv
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
return MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle.
|
||||
*/
|
||||
fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
|
||||
return MockServices.MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ package net.corda.testing.node
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.NodeParameters
|
||||
import net.corda.testing.node.internal.TestCordappImpl
|
||||
import net.corda.testing.node.internal.ScanPackageTestCordapp
|
||||
import net.corda.testing.node.internal.UriTestCordapp
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Encapsulates a CorDapp that exists on the current classpath, which can be pulled in for testing. Use [TestCordapp.findCordapp]
|
||||
@ -25,6 +28,12 @@ abstract class TestCordapp {
|
||||
/** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */
|
||||
abstract fun withConfig(config: Map<String, Any>): TestCordapp
|
||||
|
||||
/**
|
||||
* Returns a copy of this [TestCordapp] signed with a development signing key. The same signing key will be used for all signed
|
||||
* [TestCordapp]s. If the CorDapp jar is already signed, then the new jar created will its signing key replaced by the development key.
|
||||
*/
|
||||
abstract fun asSigned(): TestCordapp
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Scans the current classpath to find the CorDapp that contains the given package. All the CorDapp's metdata present in its
|
||||
@ -34,6 +43,14 @@ abstract class TestCordapp {
|
||||
* @param scanPackage The package name used to find the CorDapp. This does not need to be the root package of the CorDapp.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun findCordapp(scanPackage: String): TestCordapp = TestCordappImpl(scanPackage = scanPackage, config = emptyMap())
|
||||
fun findCordapp(scanPackage: String): TestCordapp = ScanPackageTestCordapp(scanPackage)
|
||||
|
||||
/**
|
||||
* [URI] location to a CorDapp jar. This may be a path on the local file system or a URL to an external resource.
|
||||
*
|
||||
* A [Path] can be converted into a [URI] with [Path.toUri].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun of(uri: URI): TestCordapp = UriTestCordapp(uri)
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,29 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import io.github.classgraph.ClassGraph
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.internal.cordapp.set
|
||||
import net.corda.core.internal.pooledScan
|
||||
import net.corda.core.node.services.AttachmentFixup
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.containsKey
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.attribute.FileTime
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.JarFile
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
import java.util.zip.ZipEntry
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
/**
|
||||
* Represents a completely custom CorDapp comprising of resources taken from packages on the existing classpath, even including individual
|
||||
@ -44,6 +46,8 @@ data class CustomCordapp(
|
||||
|
||||
override fun withOnlyJarContents(): CustomCordapp = CustomCordapp(packages = packages, classes = classes, fixups = fixups)
|
||||
|
||||
override fun asSigned(): CustomCordapp = signed()
|
||||
|
||||
fun signed(keyStorePath: Path? = null, numberOfSignatures: Int = 1, keyAlgorithm: String = "RSA"): CustomCordapp =
|
||||
copy(signingInfo = SigningInfo(keyStorePath, numberOfSignatures, keyAlgorithm))
|
||||
|
||||
@ -109,23 +113,6 @@ data class CustomCordapp(
|
||||
}
|
||||
}
|
||||
|
||||
private fun signJar(jarFile: Path) {
|
||||
if (signingInfo != null) {
|
||||
val keyStorePathToUse = signingInfo.keyStorePath ?: defaultJarSignerDirectory.createDirectories()
|
||||
for (i in 1 .. signingInfo.numberOfSignatures) {
|
||||
val alias = "alias$i"
|
||||
val pwd = "secret!"
|
||||
if (!keyStorePathToUse.containsKey(alias, pwd)) {
|
||||
keyStorePathToUse.generateKey(alias, pwd, "O=Test Company Ltd $i,OU=Test,L=London,C=GB", signingInfo.keyAlgorithm)
|
||||
}
|
||||
val pk = keyStorePathToUse.signJar(jarFile.toString(), alias, pwd)
|
||||
logger.debug { "Signed Jar: $jarFile with public key $pk" }
|
||||
}
|
||||
} else {
|
||||
logger.debug { "Unsigned Jar: $jarFile" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTestManifest(name: String, versionId: Int, targetPlatformVersion: Int): Manifest {
|
||||
val manifest = Manifest()
|
||||
|
||||
@ -155,13 +142,12 @@ data class CustomCordapp(
|
||||
}
|
||||
}
|
||||
|
||||
data class SigningInfo(val keyStorePath: Path?, val numberOfSignatures: Int, val keyAlgorithm: String)
|
||||
data class SigningInfo(val keyStorePath: Path?, val signatureCount: Int, val algorithm: String)
|
||||
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
private val epochFileTime = FileTime.from(Instant.EPOCH)
|
||||
private val cordappsDirectory: Path
|
||||
private val defaultJarSignerDirectory: Path
|
||||
private val whitespace = "\\s++".toRegex()
|
||||
private val cache = ConcurrentHashMap<CustomCordapp, Path>()
|
||||
|
||||
@ -169,7 +155,6 @@ data class CustomCordapp(
|
||||
val buildDir = Paths.get("build").toAbsolutePath()
|
||||
val timeDirName = getTimestampAsDirectoryName()
|
||||
cordappsDirectory = buildDir / "generated-custom-cordapps" / timeDirName
|
||||
defaultJarSignerDirectory = buildDir / "jar-signer" / timeDirName
|
||||
}
|
||||
|
||||
fun getJarFile(cordapp: CustomCordapp): Path {
|
||||
@ -179,10 +164,12 @@ data class CustomCordapp(
|
||||
val jarFile = cordappsDirectory.createDirectories() / filename
|
||||
if (it.fixups.isNotEmpty()) {
|
||||
it.createFixupJar(jarFile)
|
||||
} else if(it.packages.isNotEmpty() || it.classes.isNotEmpty() || it.fixups.isNotEmpty()) {
|
||||
it.packageAsJar(jarFile)
|
||||
} else if (it.packages.isNotEmpty() || it.classes.isNotEmpty()) {
|
||||
it.packageAsJar(jarFile)
|
||||
}
|
||||
if (it.signingInfo != null) {
|
||||
TestCordappSigner.signJar(jarFile, it.signingInfo.keyStorePath, it.signingInfo.signatureCount, it.signingInfo.algorithm)
|
||||
}
|
||||
it.signJar(jarFile)
|
||||
logger.debug { "$it packaged into $jarFile" }
|
||||
jarFile
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValue
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.RPCException
|
||||
@ -24,6 +23,7 @@ import net.corda.core.internal.concurrent.fork
|
||||
import net.corda.core.internal.concurrent.map
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.copyToDirectory
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_LICENCE
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VENDOR
|
||||
@ -35,24 +35,18 @@ import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VE
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.MIN_PLATFORM_VERSION
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION
|
||||
import net.corda.core.internal.cordapp.get
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.deleteIfExists
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.isRegularFile
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.packageName_
|
||||
import net.corda.core.internal.readObject
|
||||
import net.corda.core.internal.readText
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.internal.writeText
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
|
||||
@ -62,6 +56,7 @@ import net.corda.node.internal.DataSourceFactory
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.NodeWithInfo
|
||||
import net.corda.node.internal.clientSslOptionsCompatibleWith
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader.Companion.LEGACY_CONTRACTS_DIR_NAME
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.FlowOverride
|
||||
@ -118,8 +113,9 @@ import java.time.Instant
|
||||
import java.time.ZoneOffset.UTC
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import java.util.Collections.unmodifiableList
|
||||
import java.util.Random
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -127,10 +123,15 @@ import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.jar.JarInputStream
|
||||
import java.util.jar.Manifest
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.io.path.useDirectoryEntries
|
||||
import kotlin.io.path.writeText
|
||||
import net.corda.nodeapi.internal.config.User as InternalUser
|
||||
|
||||
class DriverDSLImpl(
|
||||
@ -266,7 +267,7 @@ class DriverDSLImpl(
|
||||
override fun startNode(parameters: NodeParameters, bytemanPort: Int?): CordaFuture<NodeHandle> {
|
||||
val p2pAddress = portAllocation.nextHostAndPort()
|
||||
// TODO: Derive name from the full picked name, don't just wrap the common name
|
||||
val name = parameters.providedName ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB")
|
||||
val name = parameters.providedName ?: CordaX500Name("${names.random().organisation}-${p2pAddress.port}", "London", "GB")
|
||||
|
||||
val config = createConfig(name, parameters, p2pAddress)
|
||||
if (isH2Database(config) && !inMemoryDB) {
|
||||
@ -414,11 +415,11 @@ class DriverDSLImpl(
|
||||
|
||||
while (process.isAlive) try {
|
||||
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
||||
if (response.isSuccessful && (response.body()?.string() == "started")) {
|
||||
if (response.isSuccessful && (response.body?.string() == "started")) {
|
||||
return WebserverHandle(handle.webAddress, process)
|
||||
}
|
||||
} catch (e: ConnectException) {
|
||||
log.debug("Retrying webserver info at ${handle.webAddress}")
|
||||
log.debug { "Retrying webserver info at ${handle.webAddress}" }
|
||||
}
|
||||
|
||||
throw IllegalStateException("Webserver at ${handle.webAddress} has died")
|
||||
@ -577,9 +578,8 @@ class DriverDSLImpl(
|
||||
// This causes two node info files to be generated.
|
||||
startOutOfProcessMiniNode(config, arrayOf("generate-node-info")).map {
|
||||
// Once done we have to read the signed node info file that's been generated
|
||||
val nodeInfoFile = config.corda.baseDirectory.list { paths ->
|
||||
paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst()
|
||||
.get()
|
||||
val nodeInfoFile = config.corda.baseDirectory.useDirectoryEntries { paths ->
|
||||
paths.single { it.name.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }
|
||||
}
|
||||
val nodeInfo = nodeInfoFile.readObject<SignedNodeInfo>().verified()
|
||||
Pair(config.withNotaryDefinition(spec.validating), NotaryInfo(nodeInfo.legalIdentities[0], spec.validating))
|
||||
@ -718,6 +718,11 @@ class DriverDSLImpl(
|
||||
extraCustomCordapps + (cordappsForAllNodes ?: emptySet())
|
||||
)
|
||||
|
||||
if (parameters.legacyContracts.isNotEmpty()) {
|
||||
val legacyContractsDir = (baseDirectory / LEGACY_CONTRACTS_DIR_NAME).createDirectories()
|
||||
parameters.legacyContracts.forEach { (it as TestCordappInternal).jarFile.copyToDirectory(legacyContractsDir) }
|
||||
}
|
||||
|
||||
val nodeFuture = if (parameters.startInSameProcess ?: startNodesInProcess) {
|
||||
val nodeAndThreadFuture = startInProcessNode(executorService, config, allowHibernateToManageAppSchema)
|
||||
shutdownManager.registerShutdown(
|
||||
@ -848,7 +853,7 @@ class DriverDSLImpl(
|
||||
|
||||
companion object {
|
||||
private val RPC_CONNECT_POLL_INTERVAL: Duration = 100.millis
|
||||
internal val log = contextLogger()
|
||||
private val log = contextLogger()
|
||||
|
||||
// While starting with inProcess mode, we need to have different names to avoid clashes
|
||||
private val inMemoryCounter = AtomicInteger()
|
||||
@ -890,18 +895,6 @@ class DriverDSLImpl(
|
||||
CORDAPP_WORKFLOW_VERSION
|
||||
))
|
||||
|
||||
private inline fun <T> Config.withOptionalValue(key: String, obj: T?, body: (T) -> ConfigValue): Config {
|
||||
return if (obj == null) {
|
||||
this
|
||||
} else {
|
||||
withValue(key, body(obj))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> valueFor(any: T): ConfigValue = ConfigValueFactory.fromAnyRef(any)
|
||||
|
||||
private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)]
|
||||
|
||||
private fun startInProcessNode(
|
||||
executorService: ScheduledExecutorService,
|
||||
config: NodeConfig,
|
||||
@ -969,15 +962,15 @@ class DriverDSLImpl(
|
||||
val excludePackagePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" +
|
||||
"com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" +
|
||||
"com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" +
|
||||
"io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;" +
|
||||
"net.i2p**;org.apache**;" +
|
||||
"io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;org.apache**;" +
|
||||
"org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" +
|
||||
"org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" +
|
||||
"org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;" +
|
||||
"com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;)"
|
||||
val excludeClassloaderPattern = "l(net.corda.core.serialization.internal.**)"
|
||||
val quasarOptions = "m"
|
||||
val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } +
|
||||
"-javaagent:$quasarJarPath=$excludePackagePattern$excludeClassloaderPattern"
|
||||
"-javaagent:$quasarJarPath=$quasarOptions$excludePackagePattern$excludeClassloaderPattern"
|
||||
|
||||
val loggingLevel = when {
|
||||
logLevelOverride != null -> logLevelOverride
|
||||
@ -992,26 +985,27 @@ class DriverDSLImpl(
|
||||
it.addAll(extraCmdLineFlag)
|
||||
}.toList()
|
||||
|
||||
val bytemanJvmArgs = {
|
||||
val bytemanAgent = bytemanJarPath?.let {
|
||||
bytemanPort?.let {
|
||||
"-javaagent:$bytemanJarPath=port:$bytemanPort,listener:true"
|
||||
}
|
||||
val bytemanAgent = bytemanJarPath?.let {
|
||||
bytemanPort?.let {
|
||||
"-javaagent:$bytemanJarPath=port:$bytemanPort,listener:true"
|
||||
}
|
||||
listOfNotNull(bytemanAgent) +
|
||||
if (bytemanAgent != null && debugPort != null) listOf(
|
||||
}
|
||||
val bytemanJvmArgs = listOfNotNull(bytemanAgent) +
|
||||
if (bytemanAgent != null && debugPort != null) {
|
||||
listOf(
|
||||
"-Dorg.jboss.byteman.verbose=true",
|
||||
"-Dorg.jboss.byteman.debug=true"
|
||||
)
|
||||
else emptyList()
|
||||
}.invoke()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
// The following dependencies are excluded from the classpath of the created JVM,
|
||||
// so that the environment resembles a real one as close as possible.
|
||||
val cp = ProcessUtilities.defaultClassPath.filter { cpEntry ->
|
||||
val cpPathEntry = Paths.get(cpEntry)
|
||||
cpPathEntry.isRegularFile()
|
||||
&& !isTestArtifact(cpPathEntry.fileName.toString())
|
||||
&& !isTestArtifact(cpPathEntry.name)
|
||||
&& !cpPathEntry.isExcludedJar
|
||||
}
|
||||
|
||||
@ -1019,7 +1013,7 @@ class DriverDSLImpl(
|
||||
className = "net.corda.node.Corda", // cannot directly get class for this, so just use string
|
||||
arguments = arguments,
|
||||
jdwpPort = debugPort,
|
||||
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true",
|
||||
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + nodeJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true",
|
||||
workingDirectory = config.corda.baseDirectory,
|
||||
maximumHeapSize = maximumHeapSize,
|
||||
classPath = cp,
|
||||
@ -1066,10 +1060,10 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process {
|
||||
val className = "net.corda.webserver.WebServer"
|
||||
writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig())
|
||||
return ProcessUtilities.startJavaProcess(
|
||||
className = className, // cannot directly get class for this, so just use string
|
||||
className = "net.corda.webserver.WebServer", // cannot directly get class for this, so just use string
|
||||
workingDirectory = handle.baseDirectory,
|
||||
arguments = listOf(BASE_DIR, handle.baseDirectory.toString()),
|
||||
jdwpPort = debugPort,
|
||||
extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") +
|
||||
@ -1092,12 +1086,11 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
private fun NodeHandleInternal.toWebServerConfig(): Config {
|
||||
|
||||
var config = ConfigFactory.empty()
|
||||
config += "webAddress" to webAddress.toString()
|
||||
config += "myLegalName" to configuration.myLegalName.toString()
|
||||
config += "rpcAddress" to configuration.rpcOptions.address.toString()
|
||||
config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers")
|
||||
config += "rpcUsers" to configuration.rpcUsers.map { it.toConfig().root().unwrapped() }
|
||||
config += "useHTTPS" to useHTTPS
|
||||
config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString()
|
||||
|
||||
@ -1111,7 +1104,7 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
private fun createCordappsClassLoader(cordapps: Collection<TestCordappInternal>?): URLClassLoader? {
|
||||
if (cordapps == null || cordapps.isEmpty()) {
|
||||
if (cordapps.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return URLClassLoader(cordapps.map { it.jarFile.toUri().toURL() }.toTypedArray())
|
||||
@ -1267,59 +1260,7 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
|
||||
driverDsl.start()
|
||||
return dsl(coerce(driverDsl))
|
||||
} catch (exception: Throwable) {
|
||||
DriverDSLImpl.log.error("Driver shutting down because of exception", exception)
|
||||
throw exception
|
||||
} finally {
|
||||
driverDsl.shutdown()
|
||||
shutdownHook.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a helper method to allow extending of the DSL, along the lines of
|
||||
* interface SomeOtherExposedDSLInterface : DriverDSL
|
||||
* interface SomeOtherInternalDSLInterface : InternalDriverDSL, SomeOtherExposedDSLInterface
|
||||
* class SomeOtherDSL(val driverDSL : DriverDSLImpl) : InternalDriverDSL by driverDSL, SomeOtherInternalDSLInterface
|
||||
*
|
||||
* @param coerce We need this explicit coercion witness because we can't put an extra DI : D bound in a `where` clause.
|
||||
*/
|
||||
fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
|
||||
defaultParameters: DriverParameters = DriverParameters(),
|
||||
driverDslWrapper: (DriverDSLImpl) -> D,
|
||||
coerce: (D) -> DI, dsl: DI.() -> A
|
||||
): A {
|
||||
setDriverSerialization().use { _ ->
|
||||
val driverDsl = driverDslWrapper(
|
||||
DriverDSLImpl(
|
||||
portAllocation = defaultParameters.portAllocation,
|
||||
debugPortAllocation = defaultParameters.debugPortAllocation,
|
||||
systemProperties = defaultParameters.systemProperties,
|
||||
driverDirectory = defaultParameters.driverDirectory.toAbsolutePath(),
|
||||
useTestClock = defaultParameters.useTestClock,
|
||||
isDebug = defaultParameters.isDebug,
|
||||
startNodesInProcess = defaultParameters.startNodesInProcess,
|
||||
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
|
||||
extraCordappPackagesToScan = @Suppress("DEPRECATION") defaultParameters.extraCordappPackagesToScan,
|
||||
jmxPolicy = defaultParameters.jmxPolicy,
|
||||
notarySpecs = defaultParameters.notarySpecs,
|
||||
compatibilityZone = null,
|
||||
networkParameters = defaultParameters.networkParameters,
|
||||
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
|
||||
inMemoryDB = defaultParameters.inMemoryDB,
|
||||
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes),
|
||||
environmentVariables = defaultParameters.environmentVariables,
|
||||
allowHibernateToManageAppSchema = defaultParameters.allowHibernateToManageAppSchema,
|
||||
premigrateH2Database = defaultParameters.premigrateH2Database,
|
||||
notaryHandleTimeout = defaultParameters.notaryHandleTimeout
|
||||
)
|
||||
)
|
||||
val shutdownHook = addShutdownHook(driverDsl::shutdown)
|
||||
try {
|
||||
driverDsl.start()
|
||||
return dsl(coerce(driverDsl))
|
||||
} catch (exception: Throwable) {
|
||||
DriverDSLImpl.log.error("Driver shutting down because of exception", exception)
|
||||
loggerFor<DriverDSL>().error("Driver shutting down because of exception", exception)
|
||||
throw exception
|
||||
} finally {
|
||||
driverDsl.shutdown()
|
||||
@ -1334,7 +1275,7 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
|
||||
* @property publishNotaries Hook for a network map server to capture the generated [NotaryInfo] objects needed for
|
||||
* creating the network parameters. This is needed as the network map server is expected to distribute it. The callback
|
||||
* will occur on a different thread to the driver-calling thread.
|
||||
* @property rootCert If specified then the nodes will register themselves with the doorman service using [url] and expect
|
||||
* @property rootCert If specified then the nodes will register themselves with the doorman service using [SharedCompatibilityZoneParams.url] and expect
|
||||
* the registration response to be rooted at this cert. If not specified then no registration is performed and the dev
|
||||
* root cert is used as normal.
|
||||
*
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.common.configuration.parsing.internal.ConfigurationWithOptions
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.crypto.SecureHash
|
||||
@ -15,10 +13,8 @@ import net.corda.core.internal.FlowIORequest
|
||||
import net.corda.core.internal.NetworkParametersStorage
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.deleteIfExists
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.notary.NotaryService
|
||||
import net.corda.core.internal.telemetry.TelemetryServiceImpl
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.MessageRecipients
|
||||
@ -27,7 +23,6 @@ import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.internal.telemetry.TelemetryServiceImpl
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
@ -74,6 +69,8 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie
|
||||
import net.corda.testing.node.TestClock
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.apache.sshd.common.util.security.SecurityUtils
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import rx.Observable
|
||||
import rx.Scheduler
|
||||
import rx.internal.schedulers.CachedThreadScheduler
|
||||
@ -85,6 +82,9 @@ import java.time.Clock
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.div
|
||||
|
||||
val MOCK_VERSION_INFO = VersionInfo(PLATFORM_VERSION, "Mock release", "Mock revision", "Mock Vendor")
|
||||
|
||||
@ -116,9 +116,6 @@ data class InternalMockNodeParameters(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [StartedNode] which exposes its internal [InternalMockNetwork.MockNode] for testing.
|
||||
*/
|
||||
interface TestStartedNode {
|
||||
val internals: InternalMockNetwork.MockNode
|
||||
val info: NodeInfo
|
||||
@ -170,7 +167,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
|
||||
val autoVisibleNodes: Boolean = true) : AutoCloseable {
|
||||
companion object {
|
||||
fun createCordappClassLoader(cordapps: Collection<TestCordappInternal>?): URLClassLoader? {
|
||||
if (cordapps == null || cordapps.isEmpty()) {
|
||||
if (cordapps.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return URLClassLoader(cordapps.map { it.jarFile.toUri().toURL() }.toTypedArray())
|
||||
@ -352,7 +349,6 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
|
||||
|
||||
private val entropyCounter = AtomicReference(args.entropyRoot)
|
||||
override val log get() = staticLog
|
||||
override val transactionVerifierWorkerCount: Int get() = 1
|
||||
|
||||
private var _rxIoScheduler: Scheduler? = null
|
||||
override val rxIoScheduler: Scheduler
|
||||
@ -455,18 +451,15 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
|
||||
return if (track) {
|
||||
smm.changes.filter { it is StateMachineManager.Change.Add }.map { it.logic }.ofType(initiatedFlowClass)
|
||||
} else {
|
||||
Observable.empty<T>()
|
||||
Observable.empty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun makeNetworkParametersStorage(): NetworkParametersStorage = MockNetworkParametersStorage()
|
||||
}
|
||||
|
||||
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters()): MockNode {
|
||||
return createUnstartedNode(parameters, defaultFactory)
|
||||
}
|
||||
|
||||
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> MockNode): MockNode {
|
||||
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(),
|
||||
nodeFactory: (MockNodeArgs) -> MockNode = defaultFactory): MockNode {
|
||||
return createNodeImpl(parameters, nodeFactory, false)
|
||||
}
|
||||
|
||||
@ -681,16 +674,17 @@ private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguratio
|
||||
}
|
||||
|
||||
class MockNodeFlowManager : NodeFlowManager() {
|
||||
val testingRegistrations = HashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
||||
private val testingRegistrations = HashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
||||
|
||||
override fun getFlowFactoryForInitiatingFlow(initiatedFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? {
|
||||
if (initiatedFlowClass in testingRegistrations) {
|
||||
return testingRegistrations.get(initiatedFlowClass)
|
||||
return testingRegistrations[initiatedFlowClass]
|
||||
}
|
||||
return super.getFlowFactoryForInitiatingFlow(initiatedFlowClass)
|
||||
}
|
||||
|
||||
fun registerTestingFactory(initiator: Class<out FlowLogic<*>>, factory: InitiatedFlowFactory<*>) {
|
||||
testingRegistrations.put(initiator, factory)
|
||||
testingRegistrations[initiator] = factory
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import net.corda.node.services.config.FlowTimeoutConfiguration
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
@ -18,4 +18,4 @@ fun MockNodeConfigOverrides.applyMockNodeOverrides(config: NodeConfiguration) {
|
||||
this.extraDataSourceProperties?.forEach { k, v -> it.dataSourceProperties.put(k, v) }
|
||||
this.flowTimeout?.also { fto -> doReturn(FlowTimeoutConfiguration(fto.timeout, fto.maxRestartCount, fto.backoffBase)).whenever(config).flowTimeout }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.FlowStateMachineHandle
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.readText
|
||||
import net.corda.core.internal.times
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.node.services.AttachmentFixup
|
||||
@ -22,14 +20,14 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.coretesting.internal.createTestSerializationEnv
|
||||
import net.corda.coretesting.internal.inVMExecutors
|
||||
import net.corda.node.services.api.StartedNodeServices
|
||||
import net.corda.node.services.messaging.Message
|
||||
import net.corda.node.services.statemachine.Checkpoint
|
||||
import net.corda.testing.driver.DriverDSL
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.internal.chooseIdentity
|
||||
import net.corda.coretesting.internal.createTestSerializationEnv
|
||||
import net.corda.coretesting.internal.inVMExecutors
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.TestCordapp
|
||||
import net.corda.testing.node.User
|
||||
@ -50,6 +48,8 @@ import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTestUtils")
|
||||
@ -61,7 +61,7 @@ private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTe
|
||||
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the flows as well.
|
||||
*/
|
||||
@JvmField
|
||||
val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.contracts")
|
||||
val FINANCE_CONTRACTS_CORDAPP: ScanPackageTestCordapp = findCordapp("net.corda.finance.contracts")
|
||||
|
||||
/**
|
||||
* Reference to the finance-workflows CorDapp in this repo. The metadata is taken directly from finance/workflows/build.gradle, including the
|
||||
@ -70,10 +70,10 @@ val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.
|
||||
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the contract classes as well.
|
||||
*/
|
||||
@JvmField
|
||||
val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.workflows")
|
||||
val FINANCE_WORKFLOWS_CORDAPP: ScanPackageTestCordapp = findCordapp("net.corda.finance.workflows")
|
||||
|
||||
@JvmField
|
||||
val FINANCE_CORDAPPS: Set<TestCordappImpl> = setOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
|
||||
val FINANCE_CORDAPPS: Set<ScanPackageTestCordapp> = setOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
|
||||
|
||||
/**
|
||||
* *Custom* CorDapp containing the contents of the `net.corda.testing.contracts` package, i.e. the dummy contracts. This is not a real CorDapp
|
||||
@ -105,9 +105,9 @@ fun cordappWithFixups(fixups: List<AttachmentFixup>) = CustomCordapp(fixups = fi
|
||||
|
||||
/**
|
||||
* Find the single CorDapp jar on the current classpath which contains the given package. This is a convenience method for
|
||||
* [TestCordapp.findCordapp] but returns the internal [TestCordappImpl].
|
||||
* [TestCordapp.findCordapp] but returns the internal [ScanPackageTestCordapp].
|
||||
*/
|
||||
fun findCordapp(scanPackage: String): TestCordappImpl = TestCordapp.findCordapp(scanPackage) as TestCordappImpl
|
||||
fun findCordapp(scanPackage: String): ScanPackageTestCordapp = TestCordapp.findCordapp(scanPackage) as ScanPackageTestCordapp
|
||||
|
||||
/** Create a *custom* CorDapp which just contains the enclosed classes of the receiver class. */
|
||||
fun Any.enclosedCordapp(): CustomCordapp {
|
||||
@ -169,8 +169,7 @@ fun addressMustBeBoundFuture(executorService: ScheduledExecutorService, hostAndP
|
||||
}
|
||||
try {
|
||||
Socket(hostAndPort.host, hostAndPort.port).close()
|
||||
Unit
|
||||
} catch (_exception: SocketException) {
|
||||
} catch (_: SocketException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@ -188,7 +187,7 @@ fun nodeMustBeStartedFuture(
|
||||
throw exception()
|
||||
}
|
||||
when {
|
||||
logFile.readText().contains("Running P2PMessaging loop") -> {
|
||||
"Running P2PMessaging loop" in logFile.readText() -> {
|
||||
Unit
|
||||
}
|
||||
Instant.now().isAfter(stopPolling) -> {
|
||||
@ -217,9 +216,7 @@ fun addressMustNotBeBoundFuture(executorService: ScheduledExecutorService, hostA
|
||||
try {
|
||||
Socket(hostAndPort.host, hostAndPort.port).close()
|
||||
null
|
||||
} catch (_exception: SocketException) {
|
||||
Unit
|
||||
}
|
||||
} catch (_: SocketException) { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,6 +301,10 @@ fun DriverDSL.assertUncompletedCheckpoints(name: CordaX500Name, expected: Long)
|
||||
}
|
||||
}
|
||||
|
||||
val nodeJvmArgs: List<String> by lazy {
|
||||
DriverDSLImpl::class.java.getResourceAsStream("node-jvm-args.txt")!!.use { it.bufferedReader().readLines() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be used by Driver and MockNode.
|
||||
*/
|
||||
|
@ -5,26 +5,27 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.coretesting.internal.testThreadFactory
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.FlowManager
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.NodeFlowManager
|
||||
import net.corda.node.internal.NodeWithInfo
|
||||
import net.corda.node.services.config.*
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.FlowOverrideConfig
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.configOf
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.node.services.config.plus
|
||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||
import net.corda.nodeapi.internal.config.toConfig
|
||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.coretesting.internal.testThreadFactory
|
||||
import net.corda.testing.node.User
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -34,7 +35,8 @@ import rx.internal.schedulers.CachedThreadScheduler
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
|
||||
// TODO Some of the logic here duplicates what's in the driver - the reason why it's not straightforward to replace it by
|
||||
// using DriverDSLImpl in `init()` and `stopAllNodes()` is because of the platform version passed to nodes (driver doesn't
|
||||
@ -60,7 +62,7 @@ abstract class NodeBasedTest @JvmOverloads constructor(
|
||||
private val portAllocation = incrementalPortAllocation()
|
||||
|
||||
init {
|
||||
System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase())
|
||||
System.setProperty("consoleLogLevel", Level.DEBUG.name().lowercase())
|
||||
}
|
||||
|
||||
@Before
|
||||
@ -161,12 +163,9 @@ class InProcessNode(
|
||||
configuration: NodeConfiguration,
|
||||
versionInfo: VersionInfo,
|
||||
flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides),
|
||||
allowHibernateToManageAppSchema: Boolean = true) : Node(configuration, versionInfo, false, flowManager = flowManager, allowHibernateToManageAppSchema = allowHibernateToManageAppSchema) {
|
||||
allowHibernateToManageAppSchema: Boolean = true
|
||||
) : Node(configuration, versionInfo, false, flowManager = flowManager, allowHibernateToManageAppSchema = allowHibernateToManageAppSchema) {
|
||||
override val runMigrationScripts: Boolean = true
|
||||
override fun start(): NodeInfo {
|
||||
assertFalse(isInvalidJavaVersion(), "You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8.")
|
||||
return super.start()
|
||||
}
|
||||
|
||||
override val rxIoScheduler get() = CachedThreadScheduler(testThreadFactory()).also { runOnStop += it::shutdown }
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.div
|
||||
|
||||
object ProcessUtilities {
|
||||
@Suppress("LongParameterList")
|
||||
@ -38,19 +39,16 @@ object ProcessUtilities {
|
||||
maximumHeapSize: String? = null,
|
||||
identifier: String = "",
|
||||
environmentVariables: Map<String,String> = emptyMap(),
|
||||
inheritIO: Boolean = true
|
||||
): Process {
|
||||
val command = mutableListOf<String>().apply {
|
||||
add(javaPath)
|
||||
(jdwpPort != null) && add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$jdwpPort")
|
||||
if (maximumHeapSize != null) add("-Xmx$maximumHeapSize")
|
||||
add("-XX:+UseG1GC")
|
||||
addAll(extraJvmArguments)
|
||||
add(className)
|
||||
addAll(arguments)
|
||||
}
|
||||
return ProcessBuilder(command).apply {
|
||||
if (inheritIO) inheritIO()
|
||||
environment().putAll(environmentVariables)
|
||||
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
|
||||
if (workingDirectory != null) {
|
||||
@ -63,7 +61,7 @@ object ProcessUtilities {
|
||||
}.start()
|
||||
}
|
||||
|
||||
private val javaPath = (System.getProperty("java.home") / "bin" / "java").toString()
|
||||
private val javaPath = Path(System.getProperty("java.home"), "bin", "java").toString()
|
||||
|
||||
val defaultClassPath: List<String> = System.getProperty("java.class.path").split(File.pathSeparator)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
import net.corda.core.internal.concurrent.map
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.node.NetworkParameters
|
||||
@ -59,6 +58,7 @@ import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import kotlin.io.path.div
|
||||
import net.corda.nodeapi.internal.config.User as InternalUser
|
||||
|
||||
inline fun <reified I : RPCOps> RPCDriverDSL.startInVmRpcClient(
|
||||
@ -188,23 +188,23 @@ data class RPCDriverDSL(
|
||||
private val driverDSL: DriverDSLImpl, private val externalTrace: Trace?
|
||||
) : InternalDriverDSL by driverDSL {
|
||||
private companion object {
|
||||
const val notificationAddress = "notifications"
|
||||
const val NOTIFICATION_ADDRESS = "notifications"
|
||||
|
||||
private fun ConfigurationImpl.configureCommonSettings(maxFileSize: Int, maxBufferedBytesPerClient: Long) {
|
||||
name = "RPCDriver"
|
||||
managementNotificationAddress = SimpleString(notificationAddress)
|
||||
managementNotificationAddress = SimpleString.of(NOTIFICATION_ADDRESS)
|
||||
isPopulateValidatedUser = true
|
||||
journalBufferSize_NIO = maxFileSize
|
||||
journalBufferSize_AIO = maxFileSize
|
||||
journalFileSize = maxFileSize
|
||||
queueConfigs = listOf(
|
||||
QueueConfiguration(RPCApi.RPC_SERVER_QUEUE_NAME).setAddress(RPCApi.RPC_SERVER_QUEUE_NAME).setDurable(false),
|
||||
QueueConfiguration(RPCApi.RPC_CLIENT_BINDING_REMOVALS).setAddress(notificationAddress)
|
||||
QueueConfiguration.of(RPCApi.RPC_SERVER_QUEUE_NAME).setAddress(RPCApi.RPC_SERVER_QUEUE_NAME).setDurable(false),
|
||||
QueueConfiguration.of(RPCApi.RPC_CLIENT_BINDING_REMOVALS).setAddress(NOTIFICATION_ADDRESS)
|
||||
.setFilterString(RPCApi.RPC_CLIENT_BINDING_REMOVAL_FILTER_EXPRESSION).setDurable(false),
|
||||
QueueConfiguration(RPCApi.RPC_CLIENT_BINDING_ADDITIONS).setAddress(notificationAddress)
|
||||
QueueConfiguration.of(RPCApi.RPC_CLIENT_BINDING_ADDITIONS).setAddress(NOTIFICATION_ADDRESS)
|
||||
.setFilterString(RPCApi.RPC_CLIENT_BINDING_ADDITION_FILTER_EXPRESSION).setDurable(false)
|
||||
)
|
||||
addressesSettings = mapOf(
|
||||
addressSettings = mapOf(
|
||||
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.#" to AddressSettings().apply {
|
||||
maxSizeBytes = maxBufferedBytesPerClient
|
||||
addressFullMessagePolicy = AddressFullMessagePolicy.PAGE
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import io.github.classgraph.ClassGraph
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.attributes
|
||||
import net.corda.core.internal.mapToSet
|
||||
import net.corda.core.internal.pooledScan
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.testing.node.TestCordapp
|
||||
import org.gradle.tooling.GradleConnector
|
||||
@ -9,9 +11,10 @@ import org.gradle.tooling.ProgressEvent
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.streams.toList
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.useDirectoryEntries
|
||||
|
||||
/**
|
||||
* Implementation of the public [TestCordapp] API.
|
||||
@ -21,39 +24,43 @@ import kotlin.streams.toList
|
||||
* the [scanPackage] may reference a gradle CorDapp project on the local system. In this scenerio the project's "jar" task is executed to
|
||||
* build the CorDapp jar. This allows us to inherit the CorDapp's MANIFEST information without having to do any extra processing.
|
||||
*/
|
||||
data class TestCordappImpl(val scanPackage: String, override val config: Map<String, Any>) : TestCordappInternal() {
|
||||
override fun withConfig(config: Map<String, Any>): TestCordappImpl = copy(config = config)
|
||||
data class ScanPackageTestCordapp(val scanPackage: String,
|
||||
override val config: Map<String, Any> = emptyMap(),
|
||||
val signed: Boolean = false) : TestCordappInternal() {
|
||||
override fun withConfig(config: Map<String, Any>): ScanPackageTestCordapp = copy(config = config)
|
||||
|
||||
override fun withOnlyJarContents(): TestCordappImpl = copy(config = emptyMap())
|
||||
override fun asSigned(): TestCordapp = copy(signed = true)
|
||||
|
||||
override val jarFile: Path
|
||||
get() {
|
||||
val jars = findJars(scanPackage)
|
||||
when (jars.size) {
|
||||
0 -> throw IllegalArgumentException("There are no CorDapps containing the package $scanPackage on the classpath. Make sure " +
|
||||
"the package name is correct and that the CorDapp is added as a gradle dependency.")
|
||||
1 -> return jars.first()
|
||||
else -> throw IllegalArgumentException("There is more than one CorDapp containing the package $scanPackage on the classpath " +
|
||||
"$jars. Specify a package name which is unique to the CorDapp.")
|
||||
}
|
||||
override fun withOnlyJarContents(): ScanPackageTestCordapp = copy(config = emptyMap(), signed = false)
|
||||
|
||||
override val jarFile: Path by lazy {
|
||||
val jars = findJars()
|
||||
val jar = when (jars.size) {
|
||||
0 -> throw IllegalArgumentException("There are no CorDapps containing the package $scanPackage on the classpath. Make sure " +
|
||||
"the package name is correct and that the CorDapp is added as a gradle dependency.")
|
||||
1 -> jars.first()
|
||||
else -> throw IllegalArgumentException("There is more than one CorDapp containing the package $scanPackage on the classpath " +
|
||||
"$jars. Specify a package name which is unique to the CorDapp.")
|
||||
}
|
||||
if (signed) TestCordappSigner.signJarCopy(jar) else jar
|
||||
}
|
||||
|
||||
private fun findJars(): Set<Path> {
|
||||
val rootPaths = findRootPaths(scanPackage)
|
||||
return if (rootPaths.all { it.toString().endsWith(".jar") }) {
|
||||
// We don't need to do anything more if all the root paths are jars
|
||||
rootPaths
|
||||
} else {
|
||||
// Otherwise we need to build those paths which are local projects and extract the built jar from them
|
||||
rootPaths.mapToSet { if (it.toString().endsWith(".jar")) it else buildCordappJar(it) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val packageToRootPaths = ConcurrentHashMap<String, Set<Path>>()
|
||||
private val projectRootToBuiltJar = ConcurrentHashMap<Path, Path>()
|
||||
private val log = contextLogger()
|
||||
|
||||
fun findJars(scanPackage: String): Set<Path> {
|
||||
val rootPaths = findRootPaths(scanPackage)
|
||||
return if (rootPaths.all { it.toString().endsWith(".jar") }) {
|
||||
// We don't need to do anything more if all the root paths are jars
|
||||
rootPaths
|
||||
} else {
|
||||
// Otherwise we need to build those paths which are local projects and extract the built jar from them
|
||||
rootPaths.mapTo(HashSet()) { if (it.toString().endsWith(".jar")) it else buildCordappJar(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun findRootPaths(scanPackage: String): Set<Path> {
|
||||
return packageToRootPaths.computeIfAbsent(scanPackage) {
|
||||
val classGraph = ClassGraph().acceptPaths(scanPackage.replace('.', '/'))
|
||||
@ -87,11 +94,8 @@ data class TestCordappImpl(val scanPackage: String, override val config: Map<Str
|
||||
runGradleBuild(projectRoot)
|
||||
|
||||
val libs = projectRoot / "build" / "libs"
|
||||
val jars = libs.list {
|
||||
it.filter { it.toString().endsWith(".jar") }
|
||||
.filter { !it.toString().endsWith("sources.jar") }
|
||||
.filter { !it.toString().endsWith("javadoc.jar") }
|
||||
.toList()
|
||||
val jars = libs.useDirectoryEntries("*.jar") { jars ->
|
||||
jars.filter { !it.toString().endsWith("sources.jar") && !it.toString().endsWith("javadoc.jar") }.toList()
|
||||
}.sortedBy { it.attributes().creationTime() }
|
||||
checkNotNull(jars.lastOrNull()) { "No jars were built in $libs" }
|
||||
}
|
@ -2,12 +2,13 @@ package net.corda.testing.node.internal
|
||||
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.core.internal.copyToDirectory
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.writeText
|
||||
import net.corda.testing.node.TestCordapp
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
/**
|
||||
* Extends the public [TestCordapp] API with internal extensions for use within the testing framework and for internal testing of the platform.
|
||||
@ -45,7 +46,7 @@ abstract class TestCordappInternal : TestCordapp() {
|
||||
// Ignore if the node already has the same CorDapp jar. This can happen if the node is being restarted.
|
||||
}
|
||||
val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render()
|
||||
(configDir / "${jar.fileName.toString().removeSuffix(".jar")}.conf").writeText(configString)
|
||||
(configDir / "${jar.name.removeSuffix(".jar")}.conf").writeText(configString)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,45 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.containsKey
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.unsignJar
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.copyTo
|
||||
import kotlin.io.path.name
|
||||
|
||||
object TestCordappSigner {
|
||||
private val defaultSignerDir = Files.createTempDirectory("testcordapp-signer")
|
||||
|
||||
init {
|
||||
defaultSignerDir.generateKey(alias = "testcordapp")
|
||||
Runtime.getRuntime().addShutdownHook(Thread(defaultSignerDir::deleteRecursively))
|
||||
}
|
||||
|
||||
fun signJarCopy(jar: Path, signerDir: Path? = null, signatureCount: Int = 1, algorithm: String = "RSA"): Path {
|
||||
val copy = Files.createTempFile(jar.name, ".jar")
|
||||
copy.toFile().deleteOnExit()
|
||||
jar.copyTo(copy, overwrite = true)
|
||||
signJar(copy, signerDir, signatureCount, algorithm)
|
||||
return copy
|
||||
}
|
||||
|
||||
fun signJar(jar: Path, signerDir: Path? = null, signatureCount: Int = 1, algorithm: String = "RSA") {
|
||||
jar.unsignJar()
|
||||
val signerDirToUse = signerDir ?: defaultSignerDir
|
||||
for (i in 1 .. signatureCount) {
|
||||
// Note in the jarsigner tool if -sigfile is not specified then the first 8 chars of alias are used as the file
|
||||
// name for the .SF and .DSA files. (See jarsigner doc). So $i below needs to be at beginning so unique files are
|
||||
// created.
|
||||
val alias = "$i-testcordapp-$algorithm"
|
||||
val password = "secret!"
|
||||
if (!signerDirToUse.containsKey(alias, password)) {
|
||||
signerDirToUse.generateKey(alias, password, "O=Test Company Ltd $i,OU=Test,L=London,C=GB", algorithm)
|
||||
}
|
||||
signerDirToUse.signJar(jar.absolutePathString(), alias, password)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.utilities.Try
|
||||
import net.corda.core.utilities.Try.Failure
|
||||
import net.corda.core.utilities.Try.Success
|
||||
import net.corda.testing.node.TestCordapp
|
||||
import java.net.URI
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
import kotlin.io.path.toPath
|
||||
|
||||
data class UriTestCordapp(val uri: URI,
|
||||
override val config: Map<String, Any> = emptyMap(),
|
||||
val signed: Boolean = false) : TestCordappInternal() {
|
||||
override fun withConfig(config: Map<String, Any>): TestCordapp = copy(config = config)
|
||||
|
||||
override fun asSigned(): TestCordapp = copy(signed = true)
|
||||
|
||||
override fun withOnlyJarContents(): TestCordappInternal = copy(config = emptyMap(), signed = false)
|
||||
|
||||
override val jarFile: Path by lazy {
|
||||
val toPathAttempt = Try.on(uri::toPath)
|
||||
when (toPathAttempt) {
|
||||
is Success -> if (signed) TestCordappSigner.signJarCopy(toPathAttempt.value) else toPathAttempt.value
|
||||
is Failure -> {
|
||||
// URI is not a local path, so we copy it to a temp file and use that.
|
||||
val downloaded = Files.createTempFile("test-cordapp-${uri.path.substringAfterLast("/").substringBeforeLast(".jar")}", ".jar")
|
||||
downloaded.toFile().deleteOnExit()
|
||||
uri.toURL().openStream().use { it.copyTo(downloaded, REPLACE_EXISTING) }
|
||||
if (signed) {
|
||||
TestCordappSigner.signJar(downloaded)
|
||||
}
|
||||
downloaded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,10 @@
|
||||
|
||||
package net.corda.testing.node.internal.network
|
||||
|
||||
import jakarta.ws.rs.GET
|
||||
import jakarta.ws.rs.Path
|
||||
import jakarta.ws.rs.Produces
|
||||
import jakarta.ws.rs.core.Response
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.internal.toX500Name
|
||||
@ -24,11 +28,11 @@ import org.bouncycastle.asn1.x509.DistributionPointName
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralNames
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.ee10.servlet.ServletHolder
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.servlet.ServletHolder
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection
|
||||
import org.glassfish.jersey.server.ResourceConfig
|
||||
import org.glassfish.jersey.servlet.ServletContainer
|
||||
import java.io.Closeable
|
||||
@ -40,10 +44,6 @@ import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.Produces
|
||||
import javax.ws.rs.core.Response
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class CrlServer(hostAndPort: NetworkHostAndPort) : Closeable {
|
||||
@ -79,7 +79,7 @@ class CrlServer(hostAndPort: NetworkHostAndPort) : Closeable {
|
||||
}
|
||||
|
||||
private val server: Server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
||||
handler = HandlerCollection().apply {
|
||||
handler = ContextHandlerCollection().apply {
|
||||
addHandler(buildServletContextHandler())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,15 @@
|
||||
package net.corda.testing.node.internal.network
|
||||
|
||||
import jakarta.ws.rs.Consumes
|
||||
import jakarta.ws.rs.GET
|
||||
import jakarta.ws.rs.POST
|
||||
import jakarta.ws.rs.Path
|
||||
import jakarta.ws.rs.PathParam
|
||||
import jakarta.ws.rs.Produces
|
||||
import jakarta.ws.rs.core.MediaType
|
||||
import jakarta.ws.rs.core.Response
|
||||
import jakarta.ws.rs.core.Response.ok
|
||||
import jakarta.ws.rs.core.Response.status
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
@ -14,11 +24,11 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.network.NetworkMap
|
||||
import net.corda.nodeapi.internal.network.ParametersUpdate
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.ee10.servlet.ServletHolder
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.servlet.ServletHolder
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection
|
||||
import org.glassfish.jersey.server.ResourceConfig
|
||||
import org.glassfish.jersey.servlet.ServletContainer
|
||||
import java.io.Closeable
|
||||
@ -29,11 +39,6 @@ import java.security.SignatureException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
import javax.ws.rs.core.Response.ok
|
||||
import javax.ws.rs.core.Response.status
|
||||
|
||||
class NetworkMapServer(private val pollInterval: Duration,
|
||||
hostAndPort: NetworkHostAndPort = NetworkHostAndPort("localhost", 0),
|
||||
@ -54,7 +59,7 @@ class NetworkMapServer(private val pollInterval: Duration,
|
||||
|
||||
init {
|
||||
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
||||
handler = HandlerCollection().apply {
|
||||
handler = ContextHandlerCollection().apply {
|
||||
addHandler(ServletContextHandler().apply {
|
||||
contextPath = "/"
|
||||
val resourceConfig = ResourceConfig().apply {
|
||||
@ -232,7 +237,7 @@ class NetworkMapServer(private val pollInterval: Duration,
|
||||
null
|
||||
}
|
||||
requireNotNull(requestedParameters)
|
||||
return Response.ok(requestedParameters!!.serialize().bytes).build()
|
||||
return Response.ok(requestedParameters.serialize().bytes).build()
|
||||
}
|
||||
|
||||
@GET
|
||||
|
@ -14,11 +14,12 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP
|
||||
import net.corda.testing.node.internal.enclosedCordapp
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.Random
|
||||
|
||||
class CustomNotaryTest {
|
||||
private lateinit var mockNet: MockNetwork
|
||||
@ -50,13 +51,15 @@ class CustomNotaryTest {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
@Test(expected = CustomNotaryException::class, timeout=300_000)
|
||||
@Test(timeout=300_000)
|
||||
fun `custom notary service is active`() {
|
||||
val tx = DummyContract.generateInitial(Random().nextInt(), notary, alice.ref(0))
|
||||
val stx = aliceNode.services.signInitialTransaction(tx)
|
||||
val future = aliceNode.startFlow(NotaryFlow.Client(stx))
|
||||
mockNet.runNetwork()
|
||||
future.getOrThrow()
|
||||
assertThatExceptionOfType(CustomNotaryException::class.java).isThrownBy {
|
||||
future.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
class CustomNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : NotaryService() {
|
||||
|
@ -2,13 +2,13 @@ package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.internal.cordapp.get
|
||||
import net.corda.core.internal.inputStream
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Path
|
||||
import java.util.jar.JarInputStream
|
||||
import kotlin.io.path.inputStream
|
||||
|
||||
class CustomCordappTest {
|
||||
@Rule
|
||||
|
@ -1,11 +1,18 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
|
||||
description 'Utilities needed for smoke tests in Corda'
|
||||
|
||||
dependencies {
|
||||
api project(':test-common')
|
||||
|
||||
// Smoke tests do NOT have any Node code on the classpath!
|
||||
compile project(':test-common')
|
||||
compile project(':client:rpc')
|
||||
implementation project(':core')
|
||||
implementation project(':node-api')
|
||||
implementation project(':client:rpc')
|
||||
|
||||
implementation "com.google.guava:guava:$guava_version"
|
||||
implementation "com.typesafe:config:$typesafe_config_version"
|
||||
implementation "org.slf4j:slf4j-api:$slf4j_version"
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar) {
|
||||
@ -14,4 +21,4 @@ tasks.named('jar', Jar) {
|
||||
// Driver will not include it as part of an out-of-process node.
|
||||
attributes('Corda-Testing': true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,28 @@
|
||||
package net.corda.smoketesting
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory.empty
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValue
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.config.toConfig
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
class NodeConfig(
|
||||
class NodeParams @JvmOverloads constructor(
|
||||
val legalName: CordaX500Name,
|
||||
val p2pPort: Int,
|
||||
val rpcPort: Int,
|
||||
val rpcAdminPort: Int,
|
||||
val isNotary: Boolean,
|
||||
val users: List<User>,
|
||||
val devMode: Boolean = true
|
||||
val cordappJars: List<Path> = emptyList(),
|
||||
val legacyContractJars: List<Path> = emptyList(),
|
||||
val jarDirs: List<Path> = emptyList(),
|
||||
val clientRpcConfig: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||
val devMode: Boolean = true,
|
||||
val version: String? = null
|
||||
) {
|
||||
companion object {
|
||||
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
||||
@ -24,12 +30,7 @@ class NodeConfig(
|
||||
|
||||
val commonName: String get() = legalName.organisation
|
||||
|
||||
/*
|
||||
* The configuration object depends upon the networkMap,
|
||||
* which is mutable.
|
||||
*/
|
||||
//TODO Make use of Any.toConfig
|
||||
private fun toFileConfig(): Config {
|
||||
fun createNodeConfig(isNotary: Boolean): String {
|
||||
val config = empty()
|
||||
.withValue("myLegalName", valueFor(legalName.toString()))
|
||||
.withValue("p2pAddress", addressValueFor(p2pPort))
|
||||
@ -39,16 +40,15 @@ class NodeConfig(
|
||||
.root())
|
||||
.withValue("rpcUsers", valueFor(users.map { it.toConfig().root().unwrapped() }.toList()))
|
||||
.withValue("useTestClock", valueFor(true))
|
||||
.withValue("jarDirs", valueFor(jarDirs.map(Path::absolutePathString)))
|
||||
.withValue("devMode", valueFor(devMode))
|
||||
return if (isNotary) {
|
||||
config.withValue("notary", ConfigValueFactory.fromMap(mapOf("validating" to true)))
|
||||
} else {
|
||||
config
|
||||
}
|
||||
}.root().render(renderOptions)
|
||||
}
|
||||
|
||||
fun toText(): String = toFileConfig().root().render(renderOptions)
|
||||
|
||||
private fun <T> valueFor(any: T): ConfigValue? = ConfigValueFactory.fromAnyRef(any)
|
||||
|
||||
private fun addressValueFor(port: Int) = valueFor("localhost:$port")
|
@ -1,19 +1,20 @@
|
||||
package net.corda.smoketesting
|
||||
|
||||
import com.google.common.collect.Lists
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.CordaRPCConnection
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.internal.copyToDirectory
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.core.internal.writeText
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.rpc.client.AMQPClientSerializationScheme
|
||||
import net.corda.testing.common.internal.asContextEnv
|
||||
import net.corda.testing.common.internal.checkNotOnClasspath
|
||||
@ -23,12 +24,18 @@ import java.nio.file.Paths
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId.systemDefault
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.createDirectory
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
class NodeProcess(
|
||||
private val config: NodeConfig,
|
||||
private val nodeDir: Path,
|
||||
private val config: NodeParams,
|
||||
val nodeDir: Path,
|
||||
private val node: Process,
|
||||
private val client: CordaRPCClient
|
||||
) : AutoCloseable {
|
||||
@ -43,6 +50,7 @@ class NodeProcess(
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!node.isAlive) return
|
||||
log.info("Stopping node '${config.commonName}'")
|
||||
node.destroy()
|
||||
if (!node.waitFor(60, SECONDS)) {
|
||||
@ -56,65 +64,98 @@ class NodeProcess(
|
||||
|
||||
// TODO All use of this factory have duplicate code which is either bundling the calling module or a 3rd party module
|
||||
// as a CorDapp for the nodes.
|
||||
class Factory(private val buildDirectory: Path = Paths.get("build")) {
|
||||
val cordaJar: Path by lazy {
|
||||
val cordaJarUrl = requireNotNull(this::class.java.getResource("/corda.jar")) {
|
||||
"corda.jar could not be found in classpath"
|
||||
}
|
||||
cordaJarUrl.toPath()
|
||||
}
|
||||
class Factory(
|
||||
private val baseNetworkParameters: NetworkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION),
|
||||
private val buildDirectory: Path = Paths.get("build")
|
||||
) : AutoCloseable {
|
||||
companion object {
|
||||
private val formatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss.SSS").withZone(systemDefault())
|
||||
private val cordaJars = ConcurrentHashMap<String, CordaJar>()
|
||||
|
||||
private companion object {
|
||||
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
|
||||
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss.SSS").withZone(systemDefault())
|
||||
init {
|
||||
checkNotOnClasspath("net.corda.node.Corda") {
|
||||
"Smoke test has the node in its classpath. Please remove the offending dependency."
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getCordaJarInfo(version: String): CordaJar {
|
||||
return cordaJars.computeIfAbsent(version) {
|
||||
val (javaHome, versionSuffix) = if (version.isEmpty()) {
|
||||
System.getProperty("java.home") to ""
|
||||
} else {
|
||||
val javaHome = if (version.split(".")[1].toInt() > 11) {
|
||||
System.getProperty("java.home")
|
||||
} else {
|
||||
// 4.11 and below need JDK 8 to run
|
||||
checkNotNull(System.getenv("JAVA_8_HOME")) { "Please set JAVA_8_HOME env variable to home directory of JDK 8" }
|
||||
}
|
||||
javaHome to "-$version"
|
||||
}
|
||||
val cordaJar = this::class.java.getResource("/corda$versionSuffix.jar")!!.toPath()
|
||||
CordaJar(cordaJar, Path(javaHome, "bin", "java"))
|
||||
}
|
||||
}
|
||||
|
||||
fun getCordaJar(version: String? = null): Path = getCordaJarInfo(version ?: "").jarPath
|
||||
}
|
||||
|
||||
private val nodesDirectory: Path = (buildDirectory / "smoke-testing" / formatter.format(Instant.now())).createDirectories()
|
||||
private val nodeInfoFilesCopier = NodeInfoFilesCopier()
|
||||
private var nodes: MutableList<NodeProcess>? = ArrayList()
|
||||
private lateinit var networkParametersCopier: NetworkParametersCopier
|
||||
|
||||
private val nodesDirectory = (buildDirectory / formatter.format(Instant.now())).createDirectories()
|
||||
fun baseDirectory(config: NodeParams): Path = nodesDirectory / config.commonName
|
||||
|
||||
private var notaryParty: Party? = null
|
||||
fun createNotaries(first: NodeParams, vararg rest: NodeParams): List<NodeProcess> {
|
||||
check(!::networkParametersCopier.isInitialized) { "Notaries have already been created" }
|
||||
|
||||
private fun createNetworkParameters(notaryInfo: NotaryInfo, nodeDir: Path) {
|
||||
try {
|
||||
networkParametersCopier = NetworkParametersCopier(testNetworkParameters(notaries = listOf(notaryInfo)))
|
||||
val notariesParams = Lists.asList(first, rest)
|
||||
val notaryInfos = notariesParams.map { notaryParams ->
|
||||
val nodeDir = baseDirectory(notaryParams).createDirectories()
|
||||
val notaryParty = DevIdentityGenerator.installKeyStoreWithNodeIdentity(nodeDir, notaryParams.legalName)
|
||||
NotaryInfo(notaryParty, true)
|
||||
}
|
||||
val networkParameters = baseNetworkParameters.copy(notaries = notaryInfos)
|
||||
networkParametersCopier = try {
|
||||
NetworkParametersCopier(networkParameters)
|
||||
} catch (_: IllegalStateException) {
|
||||
// Assuming serialization env not in context.
|
||||
AMQPClientSerializationScheme.createSerializationEnv().asContextEnv {
|
||||
networkParametersCopier = NetworkParametersCopier(testNetworkParameters(notaries = listOf(notaryInfo)))
|
||||
NetworkParametersCopier(networkParameters)
|
||||
}
|
||||
}
|
||||
networkParametersCopier.install(nodeDir)
|
||||
|
||||
return notariesParams.map { createNode(it, isNotary = true) }
|
||||
}
|
||||
|
||||
fun baseDirectory(config: NodeConfig): Path = nodesDirectory / config.commonName
|
||||
fun createNode(params: NodeParams): NodeProcess = createNode(params, isNotary = false)
|
||||
|
||||
fun create(config: NodeConfig): NodeProcess {
|
||||
val nodeDir = baseDirectory(config).createDirectories()
|
||||
private fun createNode(params: NodeParams, isNotary: Boolean): NodeProcess {
|
||||
check(::networkParametersCopier.isInitialized) { "Notary not created. Please call `creatNotaries` first." }
|
||||
|
||||
val nodeDir = baseDirectory(params).createDirectories()
|
||||
log.info("Node directory: {}", nodeDir)
|
||||
if (config.isNotary) {
|
||||
require(notaryParty == null) { "Only one notary can be created." }
|
||||
notaryParty = DevIdentityGenerator.installKeyStoreWithNodeIdentity(nodeDir, config.legalName)
|
||||
} else {
|
||||
require(notaryParty != null) { "Notary not created. Please call `create` with a notary config first." }
|
||||
val cordappsDir = (nodeDir / CORDAPPS_DIR_NAME).createDirectory()
|
||||
params.cordappJars.forEach { it.copyToDirectory(cordappsDir) }
|
||||
if (params.legacyContractJars.isNotEmpty()) {
|
||||
val legacyContractsDir = (nodeDir / "legacy-contracts").createDirectories()
|
||||
params.legacyContractJars.forEach { it.copyToDirectory(legacyContractsDir) }
|
||||
}
|
||||
(nodeDir / "node.conf").writeText(params.createNodeConfig(isNotary))
|
||||
networkParametersCopier.install(nodeDir)
|
||||
nodeInfoFilesCopier.addConfig(nodeDir)
|
||||
|
||||
(nodeDir / "node.conf").writeText(config.toText())
|
||||
createNetworkParameters(NotaryInfo(notaryParty!!, true), nodeDir)
|
||||
|
||||
createSchema(nodeDir)
|
||||
val process = startNode(nodeDir)
|
||||
val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort))
|
||||
waitForNode(process, config, client)
|
||||
return NodeProcess(config, nodeDir, process, client)
|
||||
createSchema(nodeDir, params.version)
|
||||
val process = startNode(nodeDir, params.version)
|
||||
val client = CordaRPCClient(NetworkHostAndPort("localhost", params.rpcPort), params.clientRpcConfig)
|
||||
waitForNode(process, params, client)
|
||||
val nodeProcess = NodeProcess(params, nodeDir, process, client)
|
||||
nodes!! += nodeProcess
|
||||
return nodeProcess
|
||||
}
|
||||
|
||||
private fun waitForNode(process: Process, config: NodeConfig, client: CordaRPCClient) {
|
||||
private fun waitForNode(process: Process, config: NodeParams, client: CordaRPCClient) {
|
||||
val executor = Executors.newSingleThreadScheduledExecutor()
|
||||
try {
|
||||
executor.scheduleWithFixedDelay({
|
||||
@ -129,7 +170,7 @@ class NodeProcess(
|
||||
// Cancel the "setup" task now that we've created the RPC client.
|
||||
executor.shutdown()
|
||||
} catch (e: Exception) {
|
||||
log.warn("Node '{}' not ready yet (Error: {})", config.commonName, e.message)
|
||||
log.debug("Node '{}' not ready yet (Error: {})", config.commonName, e.message)
|
||||
}
|
||||
}, 5, 1, SECONDS)
|
||||
|
||||
@ -147,30 +188,42 @@ class NodeProcess(
|
||||
class SchemaCreationFailedError(nodeDir: Path) : Exception("Creating node schema failed for $nodeDir")
|
||||
|
||||
|
||||
private fun createSchema(nodeDir: Path){
|
||||
val process = startNode(nodeDir, arrayOf("run-migration-scripts", "--core-schemas", "--app-schemas"))
|
||||
private fun createSchema(nodeDir: Path, version: String?) {
|
||||
val process = startNode(nodeDir, version, "run-migration-scripts", "--core-schemas", "--app-schemas")
|
||||
if (!process.waitFor(schemaCreationTimeOutSeconds, SECONDS)) {
|
||||
process.destroy()
|
||||
process.destroyForcibly()
|
||||
throw SchemaCreationTimedOutError(nodeDir)
|
||||
}
|
||||
if (process.exitValue() != 0){
|
||||
if (process.exitValue() != 0) {
|
||||
throw SchemaCreationFailedError(nodeDir)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SpreadOperator")
|
||||
private fun startNode(nodeDir: Path, extraArgs: Array<String> = emptyArray()): Process {
|
||||
private fun startNode(nodeDir: Path, version: String?, vararg extraArgs: String): Process {
|
||||
val cordaJar = getCordaJarInfo(version ?: "")
|
||||
val command = arrayListOf("${cordaJar.javaPath}", "-Dcapsule.log=verbose", "-jar", "${cordaJar.jarPath}", "--logging-level=debug")
|
||||
command += extraArgs
|
||||
val now = formatter.format(Instant.now())
|
||||
val builder = ProcessBuilder()
|
||||
.command(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString(), *extraArgs)
|
||||
.command(command)
|
||||
.directory(nodeDir.toFile())
|
||||
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
||||
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
|
||||
|
||||
.redirectError((nodeDir / "$now-stderr.log").toFile())
|
||||
.redirectOutput((nodeDir / "$now-stdout.log").toFile())
|
||||
builder.environment().putAll(mapOf(
|
||||
"CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString()
|
||||
))
|
||||
|
||||
return builder.start()
|
||||
val process = builder.start()
|
||||
Runtime.getRuntime().addShutdownHook(Thread(process::destroyForcibly))
|
||||
return process
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
nodes?.parallelStream()?.forEach { it.close() }
|
||||
nodes = null
|
||||
nodeInfoFilesCopier.close()
|
||||
}
|
||||
|
||||
private data class CordaJar(val jarPath: Path, val javaPath: Path)
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
|
||||
dependencies {
|
||||
compile "info.picocli:picocli:$picocli_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version"
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
compile "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_kotlin_version"
|
||||
implementation "info.picocli:picocli:$picocli_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_kotlin_version"
|
||||
|
||||
compile "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
compile "junit:junit:${junit_version}"
|
||||
implementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
implementation "junit:junit:${junit_version}"
|
||||
|
||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
@ -22,4 +21,4 @@ tasks.named('jar', Jar) {
|
||||
// Driver will not include it as part of an out-of-process node.
|
||||
attributes('Corda-Testing': true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,27 @@
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda Test Common module'
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':node-api')
|
||||
implementation project(':core')
|
||||
implementation project(':node-api')
|
||||
|
||||
// Unit testing helpers.
|
||||
compile "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
compile "junit:junit:$junit_version"
|
||||
implementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
implementation "junit:junit:$junit_version"
|
||||
implementation "org.slf4j:slf4j-api:$slf4j_version"
|
||||
|
||||
runtimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
runtimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
runtimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
|
||||
compile 'org.hamcrest:hamcrest-library:2.1'
|
||||
compile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version"
|
||||
compile "org.mockito:mockito-core:$mockito_version"
|
||||
compile "org.assertj:assertj-core:$assertj_version"
|
||||
compile "com.natpryce:hamkrest:$hamkrest_version"
|
||||
implementation 'org.hamcrest:hamcrest-library:2.1'
|
||||
implementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
|
||||
implementation "org.mockito:mockito-core:$mockito_version"
|
||||
implementation "org.assertj:assertj-core:$assertj_version"
|
||||
implementation "com.natpryce:hamkrest:$hamkrest_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -30,6 +33,11 @@ jar {
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId jar.baseName
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package net.corda.testing.common.internal
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.isDirectory
|
||||
import net.corda.core.internal.toPath
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.isDirectory
|
||||
|
||||
object ProjectStructure {
|
||||
val projectRootDir: Path = run {
|
||||
var dir = javaClass.getResource("/").toPath()
|
||||
var dir = javaClass.getResource("/")!!.toPath()
|
||||
while (!(dir / ".git").isDirectory()) {
|
||||
dir = dir.parent
|
||||
}
|
||||
|
@ -2,31 +2,17 @@
|
||||
<Configuration status="info">
|
||||
|
||||
<Properties>
|
||||
<Property name="log-path">${sys:log-path:-logs}</Property>
|
||||
<Property name="log-name">node-${hostName}</Property>
|
||||
<Property name="archive">${log-path}/archive</Property>
|
||||
<Property name="defaultLogLevel">${sys:defaultLogLevel:-info}</Property>
|
||||
<Property name="log_path">${sys:log-path:-logs}</Property>
|
||||
<Property name="log_name">node-${hostName}</Property>
|
||||
<Property name="archive">${log_path}/archive</Property>
|
||||
<Property name="default_log_level">${sys:defaultLogLevel:-info}</Property>
|
||||
</Properties>
|
||||
|
||||
<ThresholdFilter level="trace"/>
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||
<PatternLayout>
|
||||
<ScriptPatternSelector defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}">
|
||||
<Script name="MDCSelector" language="javascript"><![CDATA[
|
||||
result = null;
|
||||
if (!logEvent.getContextData().size() == 0) {
|
||||
result = "WithMDC";
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
result;
|
||||
]]>
|
||||
</Script>
|
||||
<PatternMatch key="WithMDC" pattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg %X%n}{INFO=white,WARN=red,FATAL=bright red}"/>
|
||||
</ScriptPatternSelector>
|
||||
</PatternLayout>
|
||||
<PatternLayout pattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}"/>
|
||||
<ThresholdFilter level="trace"/>
|
||||
</Console>
|
||||
|
||||
@ -38,8 +24,8 @@
|
||||
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
|
||||
those that are older than 60 days, but keep the most recent 10 GB -->
|
||||
<RollingRandomAccessFile name="RollingFile-Appender"
|
||||
fileName="${log-path}/${log-name}.log"
|
||||
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
|
||||
fileName="${log_path}/${log_name}.log"
|
||||
filePattern="${archive}/${log_name}.%date{yyyy-MM-dd}-%i.log.gz">
|
||||
|
||||
<PatternLayout pattern="[%-5level] %date{ISO8601}{UTC}Z [%t] %c{2}.%method - %msg %X%n"/>
|
||||
|
||||
@ -50,7 +36,7 @@
|
||||
|
||||
<DefaultRolloverStrategy min="1" max="100">
|
||||
<Delete basePath="${archive}" maxDepth="1">
|
||||
<IfFileName glob="${log-name}*.log.gz"/>
|
||||
<IfFileName glob="${log_name}*.log.gz"/>
|
||||
<IfLastModified age="60d">
|
||||
<IfAny>
|
||||
<IfAccumulatedFileSize exceeds="10 GB"/>
|
||||
@ -78,7 +64,7 @@
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console-ErrorCode-Appender"/>
|
||||
</Root>
|
||||
<Logger name="net.corda" level="${defaultLogLevel}" additivity="false">
|
||||
<Logger name="net.corda" level="${default_log_level}" additivity="false">
|
||||
<AppenderRef ref="Console-ErrorCode-Appender"/>
|
||||
<AppenderRef ref="RollingFile-ErrorCode-Appender" />
|
||||
</Logger>
|
||||
|
@ -1,9 +1,9 @@
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda test-db module'
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
@ -11,7 +11,7 @@ dependencies {
|
||||
|
||||
testImplementation "org.assertj:assertj-core:$assertj_version"
|
||||
testImplementation "org.slf4j:slf4j-api:$slf4j_version"
|
||||
testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j2-impl:$log4j_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -23,6 +23,11 @@ jar {
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
}
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId jar.baseName
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,33 +2,17 @@
|
||||
<Configuration status="info">
|
||||
|
||||
<Properties>
|
||||
<Property name="log-path">${sys:log-path:-logs}</Property>
|
||||
<Property name="log-name">node-${hostName}</Property>
|
||||
<Property name="archive">${log-path}/archive</Property>
|
||||
<Property name="defaultLogLevel">${sys:defaultLogLevel:-info}</Property>
|
||||
<Property name="log_path">${sys:log-path:-logs}</Property>
|
||||
<Property name="log_name">node-${hostName}</Property>
|
||||
<Property name="archive">${log_path}/archive</Property>
|
||||
<Property name="default_log_level">${sys:defaultLogLevel:-info}</Property>
|
||||
</Properties>
|
||||
|
||||
<ThresholdFilter level="trace"/>
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||
<PatternLayout>
|
||||
<ScriptPatternSelector
|
||||
defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}">
|
||||
<Script name="MDCSelector" language="javascript"><![CDATA[
|
||||
result = null;
|
||||
if (!logEvent.getContextData().size() == 0) {
|
||||
result = "WithMDC";
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
result;
|
||||
]]>
|
||||
</Script>
|
||||
<PatternMatch key="WithMDC"
|
||||
pattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg %X%n}{INFO=white,WARN=red,FATAL=bright red}"/>
|
||||
</ScriptPatternSelector>
|
||||
</PatternLayout>
|
||||
<PatternLayout pattern="%highlight{[%level{length=5}] %date{HH:mm:ss,SSS} [%t] %c{2}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}"/>
|
||||
<ThresholdFilter level="trace"/>
|
||||
</Console>
|
||||
|
||||
@ -40,8 +24,8 @@
|
||||
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
|
||||
those that are older than 60 days, but keep the most recent 10 GB -->
|
||||
<RollingRandomAccessFile name="RollingFile-Appender"
|
||||
fileName="${log-path}/${log-name}.log"
|
||||
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
|
||||
fileName="${log_path}/${log_name}.log"
|
||||
filePattern="${archive}/${log_name}.%date{yyyy-MM-dd}-%i.log.gz">
|
||||
|
||||
<PatternLayout pattern="[%-5level] %date{ISO8601}{UTC}Z [%t] %c{2}.%method - %msg %X%n"/>
|
||||
|
||||
@ -52,7 +36,7 @@
|
||||
|
||||
<DefaultRolloverStrategy min="1" max="100">
|
||||
<Delete basePath="${archive}" maxDepth="1">
|
||||
<IfFileName glob="${log-name}*.log.gz"/>
|
||||
<IfFileName glob="${log_name}*.log.gz"/>
|
||||
<IfLastModified age="60d">
|
||||
<IfAny>
|
||||
<IfAccumulatedFileSize exceeds="10 GB"/>
|
||||
@ -80,7 +64,7 @@
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console-ErrorCode-Appender"/>
|
||||
</Root>
|
||||
<Logger name="net.corda" level="${defaultLogLevel}" additivity="false">
|
||||
<Logger name="net.corda" level="${default_log_level}" additivity="false">
|
||||
<AppenderRef ref="Console-ErrorCode-Appender"/>
|
||||
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
|
||||
</Logger>
|
||||
|
@ -1,34 +1,50 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'kotlin-jpa'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.jpa'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Testing utilities for Corda'
|
||||
|
||||
dependencies {
|
||||
compile project(':test-common')
|
||||
compile project(':core-test-utils')
|
||||
compile(project(':node')) {
|
||||
// The Node only needs this for binary compatibility with Cordapps written in Kotlin 1.1.
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jre8'
|
||||
}
|
||||
compile project(':client:mock')
|
||||
implementation project(':core')
|
||||
implementation project(':test-common')
|
||||
implementation project(':core-test-utils')
|
||||
implementation project(':node')
|
||||
implementation project(':node-api')
|
||||
implementation project(':serialization')
|
||||
implementation project(':client:jackson')
|
||||
implementation project(':client:mock')
|
||||
implementation project(':confidential-identities')
|
||||
|
||||
compile "com.google.guava:guava:$guava_version"
|
||||
implementation "com.google.guava:guava:$guava_version"
|
||||
|
||||
// Guava: Google test library (collections test suite)
|
||||
compile "com.google.guava:guava-testlib:$guava_version"
|
||||
implementation "com.google.guava:guava-testlib:$guava_version"
|
||||
|
||||
implementation "org.hibernate:hibernate-core:$hibernate_version"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
|
||||
// OkHTTP: Simple HTTP library.
|
||||
compile "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
compile project(':confidential-identities')
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
|
||||
implementation "io.reactivex:rxjava:$rxjava_version"
|
||||
implementation project(':finance:contracts')
|
||||
implementation project(':finance:workflows')
|
||||
|
||||
// JimFS: in memory java.nio filesystem. Used for test and simulation utilities.
|
||||
compile "com.google.jimfs:jimfs:1.1"
|
||||
implementation "com.google.jimfs:jimfs:1.1"
|
||||
implementation "io.dropwizard.metrics:metrics-jmx:$metrics_version"
|
||||
implementation "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
implementation group: "com.typesafe", name: "config", version: typesafe_config_version
|
||||
implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
|
||||
|
||||
testCompile "org.apache.commons:commons-lang3:3.9"
|
||||
// Bouncy castle support needed for X509 certificate manipulation
|
||||
implementation "org.bouncycastle:bcprov-lts8on:${bouncycastle_version}"
|
||||
implementation "org.bouncycastle:bcpkix-lts8on:${bouncycastle_version}"
|
||||
|
||||
testImplementation "org.apache.commons:commons-lang3:$commons_lang3_version"
|
||||
testImplementation "org.assertj:assertj-core:$assertj_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -40,6 +56,11 @@ jar {
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId jar.baseName
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import java.io.InputStream
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* This interface defines output state lookup by label. It is split from the interpreter interfaces so that outputs may
|
||||
@ -52,7 +53,7 @@ interface Verifies {
|
||||
"Expected exception containing '$expectedMessage' but raised exception had no message",
|
||||
exception
|
||||
)
|
||||
} else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
|
||||
} else if (!exceptionMessage.lowercase(Locale.getDefault()).contains(expectedMessage.lowercase(Locale.getDefault()))) {
|
||||
throw AssertionError(
|
||||
"Expected exception containing '$expectedMessage' but raised exception was '$exception'",
|
||||
exception
|
||||
|
@ -3,7 +3,6 @@ package net.corda.testing.dsl
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.NullKeys.NULL_SIGNATURE
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
@ -12,7 +11,9 @@ import net.corda.core.flows.TransactionMetadata
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.cordapp.CordappProviderInternal
|
||||
import net.corda.core.internal.notary.NotaryService
|
||||
import net.corda.core.internal.verification.ExternalVerifierHandle
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.StatesToRecord
|
||||
@ -26,11 +27,10 @@ import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.node.services.DbTransactionsResolver
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
|
||||
import net.corda.node.services.persistence.AttachmentStorageInternal
|
||||
import net.corda.node.services.persistence.toInternal
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.internal.MockCordappProvider
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.testing.internal.services.InternalMockAttachmentStorage
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import java.io.InputStream
|
||||
import java.security.PublicKey
|
||||
@ -95,7 +95,6 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
|
||||
// Implementing [ServiceHubCoreInternal] allows better use in internal Corda tests
|
||||
val services: ServicesForResolution = object : ServiceHubCoreInternal, ServiceHub by ledgerInterpreter.services {
|
||||
|
||||
// [validatedTransactions.getTransaction] needs overriding as there are no calls to
|
||||
// [ServiceHub.recordTransactions] in the test dsl
|
||||
override val validatedTransactions: TransactionStorage =
|
||||
@ -113,14 +112,7 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
ledgerInterpreter.services.attachments.let {
|
||||
// Wrapping to a [InternalMockAttachmentStorage] is needed to prevent leaking internal api
|
||||
// while still allowing the tests to work
|
||||
NodeAttachmentTrustCalculator(
|
||||
attachmentStorage = if (it is MockAttachmentStorage) {
|
||||
InternalMockAttachmentStorage(it)
|
||||
} else {
|
||||
it as AttachmentStorageInternal
|
||||
},
|
||||
cacheFactory = TestingNamedCacheFactory()
|
||||
)
|
||||
NodeAttachmentTrustCalculator(attachmentStorage = it.toInternal(), cacheFactory = TestingNamedCacheFactory())
|
||||
}
|
||||
|
||||
override fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver =
|
||||
@ -130,16 +122,23 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
ledgerInterpreter.resolveStateRef<ContractState>(stateRef)
|
||||
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
|
||||
return stateRefs.map { StateAndRef(loadState(it), it) }.toSet()
|
||||
return ledgerInterpreter.services.loadStates(stateRefs)
|
||||
}
|
||||
|
||||
override val cordappProvider: CordappProvider =
|
||||
ledgerInterpreter.services.cordappProvider
|
||||
override val cordappProvider: CordappProviderInternal
|
||||
get() = ledgerInterpreter.services.cordappProvider as CordappProviderInternal
|
||||
|
||||
override val notaryService: NotaryService? = null
|
||||
|
||||
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
|
||||
|
||||
override fun loadContractAttachment(stateRef: StateRef): Attachment {
|
||||
return ledgerInterpreter.services.loadContractAttachment(stateRef)
|
||||
}
|
||||
|
||||
override val externalVerifierHandle: ExternalVerifierHandle
|
||||
get() = throw UnsupportedOperationException("Verification of legacy transactions is not supported by TestTransactionDSLInterpreter")
|
||||
|
||||
override fun recordUnnotarisedTransaction(txn: SignedTransaction) {}
|
||||
|
||||
override fun removeUnnotarisedTransaction(id: SecureHash) {}
|
||||
@ -169,7 +168,6 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
|
||||
override fun reference(stateRef: StateRef) {
|
||||
val state = ledgerInterpreter.resolveStateRef<ContractState>(stateRef)
|
||||
@Suppress("DEPRECATION") // Will remove when feature finalised.
|
||||
transactionBuilder.addReferenceState(StateAndRef(state, stateRef).referenced())
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.corda.testing.http
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -24,17 +24,17 @@ object HttpUtils {
|
||||
}
|
||||
|
||||
fun putJson(url: URL, data: String) {
|
||||
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data)
|
||||
val body = data.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
|
||||
makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").put(body).build())
|
||||
}
|
||||
|
||||
fun postJson(url: URL, data: String) {
|
||||
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data)
|
||||
val body = data.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
|
||||
makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
|
||||
}
|
||||
|
||||
fun postPlain(url: URL, data: String) {
|
||||
val body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), data)
|
||||
val body = data.toRequestBody("text/plain; charset=utf-8".toMediaTypeOrNull())
|
||||
makeRequest(Request.Builder().url(url).post(body).build())
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ object HttpUtils {
|
||||
private fun makeRequest(request: Request) {
|
||||
val response = client.newCall(request).execute()
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("${request.method()} to ${request.url()} returned a ${response.code()}: ${response.body()?.string()}")
|
||||
throw IOException("${request.method} to ${request.url} returned a ${response.code}: ${response.body?.string()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import co.paralleluniverse.fibers.Fiber
|
||||
import co.paralleluniverse.fibers.Instrumented
|
||||
import co.paralleluniverse.fibers.Stack
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
@ -12,7 +11,6 @@ import net.corda.core.flows.FlowStackSnapshot.Frame
|
||||
import net.corda.core.flows.StackFrameDataToken
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.write
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
@ -21,8 +19,29 @@ import net.corda.node.services.statemachine.FlowStackSnapshotFactory
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import kotlin.io.path.div
|
||||
|
||||
class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
|
||||
private companion object {
|
||||
private const val QUASAR_0_7_INSTRUMENTED_CLASS_NAME = "co.paralleluniverse.fibers.Instrumented"
|
||||
private const val QUASAR_0_8_INSTRUMENTED_CLASS_NAME = "co.paralleluniverse.fibers.suspend.Instrumented"
|
||||
|
||||
// @Instrumented is an internal Quasar class that should not be referenced directly.
|
||||
// We have needed to change its package for Quasar 0.8.x.
|
||||
@Suppress("unchecked_cast")
|
||||
private val instrumentedAnnotationClass: Class<out Annotation> = try {
|
||||
Class.forName(QUASAR_0_7_INSTRUMENTED_CLASS_NAME, false, this::class.java.classLoader)
|
||||
} catch (_: ClassNotFoundException) {
|
||||
Class.forName(QUASAR_0_8_INSTRUMENTED_CLASS_NAME, false, this::class.java.classLoader)
|
||||
} as Class<out Annotation>
|
||||
|
||||
private val methodOptimized = instrumentedAnnotationClass.getMethod("methodOptimized")
|
||||
|
||||
private fun isMethodOptimized(annotation: Annotation): Boolean {
|
||||
return instrumentedAnnotationClass.isInstance(annotation) && (methodOptimized.invoke(annotation) as Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun getFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot {
|
||||
var snapshot: FlowStackSnapshot? = null
|
||||
@ -68,7 +87,7 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
|
||||
val frames = stackTraceToAnnotation.reversed().map { (element, annotation) ->
|
||||
// If annotation is null then the case indicates that this is an entry point - i.e.
|
||||
// the net.corda.node.services.statemachine.FlowStateMachineImpl.run method
|
||||
val stackObjects = if (frameObjectsIterator.hasNext() && (annotation == null || !annotation.methodOptimized)) {
|
||||
val stackObjects = if (frameObjectsIterator.hasNext() && (annotation == null || !isMethodOptimized(annotation))) {
|
||||
frameObjectsIterator.next()
|
||||
} else {
|
||||
emptyList()
|
||||
@ -78,11 +97,11 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
|
||||
return FlowStackSnapshot(Instant.now(), flowClass.name, frames)
|
||||
}
|
||||
|
||||
private val StackTraceElement.instrumentedAnnotation: Instrumented?
|
||||
private val StackTraceElement.instrumentedAnnotation: Annotation?
|
||||
get() {
|
||||
Class.forName(className).methods.forEach {
|
||||
if (it.name == methodName && it.isAnnotationPresent(Instrumented::class.java)) {
|
||||
return it.getAnnotation(Instrumented::class.java)
|
||||
Class.forName(className, false, this::class.java.classLoader).methods.forEach {
|
||||
if (it.name == methodName && it.isAnnotationPresent(instrumentedAnnotationClass)) {
|
||||
return it.getAnnotation(instrumentedAnnotationClass)
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -105,7 +124,7 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
|
||||
|
||||
private fun filterOutStackDump(flowStackSnapshot: FlowStackSnapshot): FlowStackSnapshot {
|
||||
val framesFilteredByStackTraceElement = flowStackSnapshot.stackFrames.filter {
|
||||
!FlowStateMachine::class.java.isAssignableFrom(Class.forName(it.stackTraceElement.className))
|
||||
!FlowStateMachine::class.java.isAssignableFrom(Class.forName(it.stackTraceElement.className, false, this::class.java.classLoader))
|
||||
}
|
||||
val framesFilteredByObjects = framesFilteredByStackTraceElement.map {
|
||||
it.copy(stackObjects = it.stackObjects.map {
|
||||
|
@ -7,7 +7,6 @@ import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.crypto.DigestService
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
@ -49,13 +48,11 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.ServerSocket
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.X509CRL
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.Properties
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
import java.util.zip.ZipEntry
|
||||
@ -111,7 +108,7 @@ fun createDevIntermediateCaCertPath(
|
||||
*/
|
||||
fun createDevNodeCaCertPath(
|
||||
legalName: CordaX500Name,
|
||||
nodeKeyPair: KeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
|
||||
nodeKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
|
||||
rootCaName: X500Principal = defaultRootCaName,
|
||||
intermediateCaName: X500Principal = defaultIntermediateCaName
|
||||
): Triple<CertificateAndKeyPair, CertificateAndKeyPair, CertificateAndKeyPair> {
|
||||
@ -156,7 +153,6 @@ fun fixedCrlSource(crls: Set<X509CRL>): CrlSource {
|
||||
}
|
||||
}
|
||||
|
||||
/** This is the same as the deprecated [WireTransaction] c'tor but avoids the deprecation warning. */
|
||||
@SuppressWarnings("LongParameterList")
|
||||
fun createWireTransaction(inputs: List<StateRef>,
|
||||
attachments: List<SecureHash>,
|
||||
@ -164,9 +160,10 @@ fun createWireTransaction(inputs: List<StateRef>,
|
||||
commands: List<Command<*>>,
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
legacyAttachments: List<SecureHash> = emptyList(),
|
||||
privacySalt: PrivacySalt = PrivacySalt(),
|
||||
digestService: DigestService = DigestService.default): WireTransaction {
|
||||
val componentGroups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null)
|
||||
val componentGroups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null, legacyAttachments)
|
||||
return WireTransaction(componentGroups, privacySalt, digestService)
|
||||
}
|
||||
|
||||
@ -251,23 +248,5 @@ fun <R> withTestSerializationEnvIfNotSet(block: () -> R): R {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check if particular port is already bound i.e. not vacant
|
||||
*/
|
||||
fun isLocalPortBound(port: Int): Boolean {
|
||||
return try {
|
||||
ServerSocket(port).use {
|
||||
// Successful means that the port was vacant
|
||||
false
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// Failed to open server socket means that it is already bound by someone
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val IS_OPENJ9 = System.getProperty("java.vm.name").toLowerCase().contains("openj9")
|
||||
|
||||
@JvmField
|
||||
val IS_S390X = System.getProperty("os.arch") == "s390x"
|
||||
val IS_S390X = System.getProperty("os.arch") == "s390x"
|
||||
|
@ -5,16 +5,16 @@ import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.nodeapi.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.node.services.persistence.AttachmentStorageInternal
|
||||
import net.corda.nodeapi.internal.cordapp.CordappLoader
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import java.security.PublicKey
|
||||
import java.util.jar.Attributes
|
||||
|
||||
class MockCordappProvider(
|
||||
cordappLoader: CordappLoader,
|
||||
attachmentStorage: AttachmentStorage,
|
||||
attachmentStorage: AttachmentStorageInternal,
|
||||
cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider()
|
||||
) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage) {
|
||||
|
||||
|
@ -16,12 +16,12 @@ class TestingNamedCacheFactory private constructor(private val sizeOverride: Lon
|
||||
override fun bindWithMetrics(metricRegistry: MetricRegistry): BindableNamedCacheFactory = TestingNamedCacheFactory(sizeOverride, metricRegistry, this.nodeConfiguration)
|
||||
override fun bindWithConfig(nodeConfiguration: NodeConfiguration): BindableNamedCacheFactory = TestingNamedCacheFactory(sizeOverride, this.metricRegistry, nodeConfiguration)
|
||||
|
||||
override fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V> {
|
||||
override fun <K : Any, V : Any> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V> {
|
||||
// Does not check metricRegistry or nodeConfiguration, because for tests we don't care.
|
||||
return caffeine.maximumSize(sizeOverride).build<K, V>()
|
||||
}
|
||||
|
||||
override fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> {
|
||||
override fun <K : Any, V : Any> buildNamed(caffeine: Caffeine<in K, in V>, name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> {
|
||||
// Does not check metricRegistry or nodeConfiguration, because for tests we don't care.
|
||||
val configuredCaffeine = when (name) {
|
||||
"DBTransactionStorage_transactions" -> caffeine.maximumWeight(1.MB)
|
||||
|
@ -1,43 +0,0 @@
|
||||
package net.corda.testing.internal.services
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||
import net.corda.node.services.persistence.AttachmentStorageInternal
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import java.io.InputStream
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* Internal version of [MockAttachmentStorage] that implements [AttachmentStorageInternal] for use
|
||||
* in internal tests where [AttachmentStorageInternal] functions are needed.
|
||||
*/
|
||||
class InternalMockAttachmentStorage(storage: MockAttachmentStorage) : AttachmentStorageInternal,
|
||||
AttachmentStorage by storage {
|
||||
|
||||
override fun privilegedImportAttachment(
|
||||
jar: InputStream,
|
||||
uploader: String,
|
||||
filename: String?
|
||||
): AttachmentId = importAttachment(jar, uploader, filename)
|
||||
|
||||
override fun privilegedImportOrGetAttachment(
|
||||
jar: InputStream,
|
||||
uploader: String,
|
||||
filename: String?
|
||||
): AttachmentId {
|
||||
return try {
|
||||
importAttachment(jar, uploader, filename)
|
||||
} catch (faee: java.nio.file.FileAlreadyExistsException) {
|
||||
AttachmentId.create(faee.message!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAllAttachmentsByCriteria(criteria: AttachmentQueryCriteria): Stream<Pair<String?, Attachment>> {
|
||||
return queryAttachments(criteria)
|
||||
.map(this::openAttachment)
|
||||
.map { null as String? to it!! }
|
||||
.stream()
|
||||
}
|
||||
}
|
@ -12,12 +12,16 @@ import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VER
|
||||
import net.corda.core.internal.readFully
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||
import net.corda.core.node.services.vault.AttachmentSort
|
||||
import net.corda.core.node.services.vault.Builder
|
||||
import net.corda.core.node.services.vault.ColumnPredicate
|
||||
import net.corda.core.node.services.vault.Sort
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.nodeapi.internal.withContractsInJar
|
||||
import java.io.InputStream
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
@ -33,7 +37,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
/** A map of the currently stored files by their [SecureHash] */
|
||||
val files: Map<SecureHash, Pair<Attachment, ByteArray>> get() = _files
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun importAttachment(jar: InputStream): AttachmentId = importAttachment(jar, UNKNOWN_UPLOADER, null)
|
||||
|
||||
override fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
|
||||
@ -78,11 +82,11 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
|
||||
override fun hasAttachment(attachmentId: AttachmentId) = files.containsKey(attachmentId)
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun importOrGetAttachment(jar: InputStream): AttachmentId {
|
||||
return try {
|
||||
importAttachment(jar, UNKNOWN_UPLOADER, null)
|
||||
} catch (e: java.nio.file.FileAlreadyExistsException) {
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
AttachmentId.create(e.message!!)
|
||||
}
|
||||
}
|
||||
@ -109,7 +113,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
val baseAttachment = MockAttachment({ bytes }, sha256, signers, uploader)
|
||||
val version = try { Integer.parseInt(baseAttachment.openAsJAR().manifest?.mainAttributes?.getValue(Attributes.Name.IMPLEMENTATION_VERSION)) } catch (e: Exception) { DEFAULT_CORDAPP_VERSION }
|
||||
val attachment =
|
||||
if (contractClassNames == null || contractClassNames.isEmpty()) baseAttachment
|
||||
if (contractClassNames.isNullOrEmpty()) baseAttachment
|
||||
else {
|
||||
contractClassNames.map {contractClassName ->
|
||||
val contractClassMetadata = ContractAttachmentMetadata(contractClassName, version, signers.isNotEmpty(), signers, uploader)
|
||||
|
@ -1,14 +1,13 @@
|
||||
package net.corda.testing.core
|
||||
|
||||
import net.corda.core.internal.InvalidJarSignersException
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.addIndexList
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.createJar
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.getJarSigners
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.updateJar
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.addIndexList
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.*
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.AfterClass
|
||||
@ -17,6 +16,12 @@ import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.PublicKey
|
||||
import kotlin.io.path.createDirectory
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.useDirectoryEntries
|
||||
import kotlin.io.path.writeLines
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@ -51,14 +56,12 @@ class JarSignatureCollectorTest {
|
||||
}
|
||||
}
|
||||
|
||||
private val List<Party>.keys get() = map { it.owningKey }
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
dir.list {
|
||||
it.filter { !it.fileName.toString().startsWith("_") }.forEach(Path::deleteRecursively)
|
||||
dir.useDirectoryEntries { paths ->
|
||||
paths.filter { !it.name.startsWith("_") }.forEach(Path::deleteRecursively)
|
||||
}
|
||||
assertThat(dir.list()).hasSize(5)
|
||||
assertThat(dir.listDirectoryEntries()).hasSize(5)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
@ -136,7 +139,7 @@ class JarSignatureCollectorTest {
|
||||
fun `one signer with EC algorithm`() {
|
||||
dir.createJar(FILENAME, "_signable1", "_signable2")
|
||||
// JDK11: Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -keypass value.
|
||||
val key = signAs(CHARLIE, CHARLIE_PASS)
|
||||
val key = signAs(CHARLIE)
|
||||
assertEquals(listOf(key), dir.getJarSigners(FILENAME)) // We only used CHARLIE's distinguished name, so the keys will be different.
|
||||
}
|
||||
|
||||
@ -148,15 +151,12 @@ class JarSignatureCollectorTest {
|
||||
assertEquals(listOf(key), dir.getJarSigners(FILENAME))
|
||||
}
|
||||
|
||||
private fun signAsAlice() = signAs(ALICE, ALICE_PASS)
|
||||
private fun signAsBob() = signAs(BOB, BOB_PASS)
|
||||
private fun signAsAlice() = signAs(ALICE)
|
||||
private fun signAsBob() = signAs(BOB)
|
||||
|
||||
// JDK11: Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -keypass value.
|
||||
// TODO: use programmatic API support to implement signing (see https://docs.oracle.com/javase/9/docs/api/jdk/security/jarsigner/JarSigner.html)
|
||||
private fun signAs(alias: String, keyPassword: String = alias) : PublicKey {
|
||||
return if (SystemUtils.IS_JAVA_11)
|
||||
dir.signJar(FILENAME, alias, "storepass", "storepass")
|
||||
else
|
||||
dir.signJar(FILENAME, alias, "storepass", keyPassword)
|
||||
private fun signAs(alias: String) : PublicKey {
|
||||
return dir.signJar(FILENAME, alias, "storepass", "storepass")
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda node web server'
|
||||
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
integrationTestImplementation.extendsFrom testImplementation
|
||||
integrationTestRuntime.extendsFrom testRuntimeOnly
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -25,42 +23,46 @@ processResources {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':client:rpc')
|
||||
compile project(':client:jackson')
|
||||
compile project(':tools:cliutils')
|
||||
compile project(":common-logging")
|
||||
implementation project(':core')
|
||||
implementation project(':node-api')
|
||||
implementation project(':client:rpc')
|
||||
implementation project(':client:jackson')
|
||||
implementation project(':tools:cliutils')
|
||||
implementation project(":common-logging")
|
||||
|
||||
// Web stuff: for HTTP[S] servlets
|
||||
compile "org.eclipse.jetty:jetty-servlet:$jetty_version"
|
||||
compile "org.eclipse.jetty:jetty-webapp:$jetty_version"
|
||||
compile "javax.servlet:javax.servlet-api:${servlet_version}"
|
||||
compile "commons-fileupload:commons-fileupload:$fileupload_version"
|
||||
implementation "org.eclipse.jetty.ee10:jetty-ee10-servlet:$jetty_version"
|
||||
implementation "org.eclipse.jetty.ee10:jetty-ee10-webapp:$jetty_version"
|
||||
//implementation "javax.servlet:javax.servlet-api:${servlet_version}"
|
||||
implementation "org.apache.commons:commons-fileupload2-jakarta:$fileupload_version"
|
||||
|
||||
// Log4J: logging framework (with SLF4J bindings)
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
compile "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
implementation "org.apache.logging.log4j:log4j-slf4j2-impl:$log4j_version"
|
||||
implementation "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
|
||||
// JOpt: for command line flags.
|
||||
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
|
||||
implementation "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
|
||||
|
||||
// Jersey for JAX-RS implementation for use in Jetty
|
||||
// TODO: remove force upgrade when jersey catches up
|
||||
compile "org.eclipse.jetty:jetty-continuation:${jetty_version}"
|
||||
|
||||
compile "org.glassfish.jersey.core:jersey-server:$jersey_version"
|
||||
compile "org.glassfish.jersey.containers:jersey-container-servlet:$jersey_version"
|
||||
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:$jersey_version"
|
||||
compile "org.glassfish.jersey.media:jersey-media-json-jackson:$jersey_version"
|
||||
implementation "org.glassfish.jersey.core:jersey-server:$jersey_version"
|
||||
implementation "org.glassfish.jersey.containers:jersey-container-servlet:$jersey_version"
|
||||
implementation "org.glassfish.jersey.containers:jersey-container-jetty-http:$jersey_version"
|
||||
implementation "org.glassfish.jersey.media:jersey-media-json-jackson:$jersey_version"
|
||||
implementation "org.glassfish.jersey.inject:jersey-hk2:$jersey_version"
|
||||
|
||||
// For rendering the index page.
|
||||
compile "org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.12"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.12"
|
||||
|
||||
// Capsule is a library for building independently executable fat JARs.
|
||||
// We only need this dependency to compile our Caplet against.
|
||||
compileOnly "co.paralleluniverse:capsule:$capsule_version"
|
||||
// We only need this dependency to implementation our Caplet against.
|
||||
implementation "co.paralleluniverse:capsule:$capsule_version"
|
||||
|
||||
integrationTestCompile project(':node-driver')
|
||||
implementation group: "com.typesafe", name: "config", version: typesafe_config_version
|
||||
implementation "com.google.guava:guava:$guava_version"
|
||||
|
||||
implementation "io.netty:netty-transport-native-unix-common:4.1.77.Final.jar"
|
||||
|
||||
|
||||
testImplementation project(":core-test-utils")
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
@ -68,9 +70,11 @@ dependencies {
|
||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
|
||||
integrationTestImplementation project(':node-driver')
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
tasks.register('integrationTest', Test) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
||||
@ -79,6 +83,14 @@ jar {
|
||||
baseName 'corda-testserver-impl'
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
compileJava.dependsOn ':node:capsule:buildCordaJAR'
|
||||
javadoc.dependsOn ':testing:testserver:testcapsule:buildWebserverJar'
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId jar.baseName
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,22 @@
|
||||
// must also be in the default package. When using Kotlin there are a whole host of exceptions
|
||||
// trying to construct this from Capsule, so it is written in Java.
|
||||
|
||||
import com.typesafe.config.*;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import com.typesafe.config.ConfigParseOptions;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
import sun.misc.Signal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CordaWebserverCaplet extends Capsule {
|
||||
@ -37,11 +45,6 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
}
|
||||
}
|
||||
|
||||
File getConfigFile(List<String> args, String baseDir) {
|
||||
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
|
||||
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config);
|
||||
}
|
||||
|
||||
String getBaseDirectory(List<String> args) {
|
||||
String baseDir = getOptionMultiple(args, Arrays.asList("--base-directory", "-b"));
|
||||
return Paths.get((baseDir == null) ? "." : baseDir).toAbsolutePath().normalize().toString();
|
||||
@ -69,7 +72,7 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
}
|
||||
|
||||
if (arg.toLowerCase().startsWith(lowerCaseOption)) {
|
||||
if (arg.length() > option.length() && arg.substring(option.length(), option.length() + 1).equals("=")) {
|
||||
if (arg.length() > option.length() && arg.charAt(option.length()) == '=') {
|
||||
return arg.substring(option.length() + 1);
|
||||
} else {
|
||||
return null;
|
||||
@ -87,23 +90,6 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
return super.prelaunch(jvmArgs, args);
|
||||
}
|
||||
|
||||
// Capsule does not handle multiple instances of same option hence we add in the args here to process builder
|
||||
// For multiple instances Capsule jvm args handling works on basis that one overrides the other.
|
||||
@Override
|
||||
protected int launch(ProcessBuilder pb) throws IOException, InterruptedException {
|
||||
if (isAtLeastJavaVersion11()) {
|
||||
List<String> args = pb.command();
|
||||
List<String> myArgs = Arrays.asList(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.time=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.nio=ALL-UNNAMED");
|
||||
args.addAll(1, myArgs);
|
||||
pb.command(args);
|
||||
}
|
||||
return super.launch(pb);
|
||||
}
|
||||
|
||||
// Add working directory variable to capsules string replacement variables.
|
||||
@Override
|
||||
protected String getVarValue(String var) {
|
||||
@ -157,9 +143,6 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
} catch (ConfigException e) {
|
||||
log(LOG_QUIET, e);
|
||||
}
|
||||
if (isAtLeastJavaVersion11()) {
|
||||
jvmArgs.add("-Dnashorn.args=--no-deprecation-warning");
|
||||
}
|
||||
return (T) jvmArgs;
|
||||
} else if (ATTR_SYSTEM_PROPERTIES == attr) {
|
||||
// Add system properties, if specified, from the config.
|
||||
@ -196,20 +179,12 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
|
||||
private static void checkJavaVersion() {
|
||||
String version = System.getProperty("java.version");
|
||||
if (version == null || Stream.of("1.8", "11").noneMatch(version::startsWith)) {
|
||||
System.err.printf("Error: Unsupported Java version %s; currently only version 1.8 or 11 is supported.\n", version);
|
||||
if (version == null || Stream.of("17").noneMatch(version::startsWith)) {
|
||||
System.err.printf("Error: Unsupported Java version %s; currently only version 17 is supported.\n", version);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAtLeastJavaVersion11() {
|
||||
String version = System.getProperty("java.specification.version");
|
||||
if (version != null) {
|
||||
return Float.parseFloat(version) >= 11f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Boolean checkIfCordappDirExists(File dir) {
|
||||
try {
|
||||
if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case.
|
||||
|
@ -6,12 +6,12 @@ import com.typesafe.config.ConfigParseOptions
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import joptsimple.OptionParser
|
||||
import joptsimple.util.EnumConverter
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import org.slf4j.event.Level
|
||||
import java.io.PrintStream
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.div
|
||||
|
||||
// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
|
||||
class ArgsParser {
|
||||
|
@ -3,7 +3,6 @@
|
||||
package net.corda.webserver
|
||||
|
||||
import com.typesafe.config.ConfigException
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.core.internal.location
|
||||
import net.corda.core.internal.rootCause
|
||||
@ -11,6 +10,7 @@ import net.corda.webserver.internal.NodeWebServer
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.InetAddress
|
||||
import kotlin.io.path.div
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
@ -36,7 +36,7 @@ fun main(args: Array<String>) {
|
||||
System.setProperty("consoleLogLevel", "info")
|
||||
}
|
||||
|
||||
System.setProperty("log-path", (cmdlineOptions.baseDirectory / "logs/web").toString())
|
||||
System.setProperty("log-path", (cmdlineOptions.baseDirectory / "logs" / "web").toString())
|
||||
val log = LoggerFactory.getLogger("Main")
|
||||
println("This Corda-specific web server is deprecated and will be removed in future.")
|
||||
println("Please switch to a regular web framework like Spring, J2EE or Play Framework.")
|
||||
@ -54,8 +54,6 @@ fun main(args: Array<String>) {
|
||||
val info = ManagementFactory.getRuntimeMXBean()
|
||||
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
|
||||
log.info("Application Args: ${args.joinToString(" ")}")
|
||||
// JDK 11 (bootclasspath no longer supported from JDK 9)
|
||||
if (info.isBootClassPathSupported) log.info("bootclasspath: ${info.bootClassPath}")
|
||||
log.info("classpath: ${info.classPath}")
|
||||
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
||||
log.info("Machine: ${InetAddress.getLocalHost().hostName}")
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.webserver
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.config.getValue
|
||||
|
@ -1,15 +1,15 @@
|
||||
package net.corda.webserver.api
|
||||
|
||||
import jakarta.ws.rs.GET
|
||||
import jakarta.ws.rs.Path
|
||||
import jakarta.ws.rs.Produces
|
||||
import jakarta.ws.rs.core.MediaType
|
||||
import jakarta.ws.rs.core.Response
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import java.time.LocalDateTime
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.Produces
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
/**
|
||||
* Top level interface to external interaction with the distributed ledger.
|
||||
|
@ -1,11 +1,10 @@
|
||||
package net.corda.webserver.converters
|
||||
|
||||
import jakarta.ws.rs.ext.ParamConverter
|
||||
import jakarta.ws.rs.ext.ParamConverterProvider
|
||||
import jakarta.ws.rs.ext.Provider
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import java.lang.reflect.Type
|
||||
import javax.ws.rs.ext.ParamConverter
|
||||
import javax.ws.rs.ext.ParamConverterProvider
|
||||
import javax.ws.rs.ext.Provider
|
||||
|
||||
object CordaX500NameConverter : ParamConverter<CordaX500Name> {
|
||||
override fun toString(value: CordaX500Name) = value.toString()
|
||||
@ -14,10 +13,11 @@ object CordaX500NameConverter : ParamConverter<CordaX500Name> {
|
||||
|
||||
@Provider
|
||||
object CordaConverterProvider : ParamConverterProvider {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> getConverter(rawType: Class<T>, genericType: Type?, annotations: Array<out Annotation>?): ParamConverter<T>? {
|
||||
if (rawType == CordaX500Name::class.java) {
|
||||
return uncheckedCast(CordaX500NameConverter)
|
||||
return CordaX500NameConverter as ParamConverter<T>?
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.corda.webserver.internal
|
||||
|
||||
import jakarta.ws.rs.core.Response
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.vaultQueryBy
|
||||
import net.corda.webserver.api.APIServer
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
class APIServerImpl(val rpcOps: CordaRPCOps) : APIServer {
|
||||
override fun serverTime(): LocalDateTime {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.corda.webserver.internal
|
||||
|
||||
import jakarta.ws.rs.core.Response
|
||||
import jakarta.ws.rs.ext.ExceptionMapper
|
||||
import jakarta.ws.rs.ext.Provider
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import javax.ws.rs.core.Response
|
||||
import javax.ws.rs.ext.ExceptionMapper
|
||||
import javax.ws.rs.ext.Provider
|
||||
|
||||
// Provides basic exception logging to all APIs
|
||||
@Provider
|
||||
|
@ -14,12 +14,12 @@ import net.corda.webserver.WebServerConfig
|
||||
import net.corda.webserver.converters.CordaConverterProvider
|
||||
import net.corda.webserver.services.WebServerPluginRegistry
|
||||
import net.corda.webserver.servlets.*
|
||||
import org.eclipse.jetty.ee10.servlet.DefaultServlet
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.ee10.servlet.ServletHolder
|
||||
import org.eclipse.jetty.server.*
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.DefaultServlet
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.servlet.ServletHolder
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory
|
||||
import org.glassfish.jersey.server.ResourceConfig
|
||||
import org.glassfish.jersey.server.ServerProperties
|
||||
@ -30,7 +30,6 @@ import java.io.Writer
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.BindException
|
||||
import java.util.*
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
class NodeWebServer(val config: WebServerConfig) {
|
||||
private companion object {
|
||||
@ -60,7 +59,7 @@ class NodeWebServer(val config: WebServerConfig) {
|
||||
|
||||
private fun initWebServer(localRpc: CordaRPCOps): Server {
|
||||
// Note that the web server handlers will all run concurrently, and not on the node thread.
|
||||
val handlerCollection = HandlerCollection()
|
||||
val handlerCollection = ContextHandlerCollection()
|
||||
|
||||
// API, data upload and download to services (attachments, rates oracles etc)
|
||||
handlerCollection.addHandler(buildServletContextHandler(localRpc))
|
||||
@ -72,7 +71,7 @@ class NodeWebServer(val config: WebServerConfig) {
|
||||
httpsConfiguration.outputBufferSize = 32768
|
||||
httpsConfiguration.addCustomizer(SecureRequestCustomizer())
|
||||
@Suppress("DEPRECATION")
|
||||
val sslContextFactory = SslContextFactory()
|
||||
val sslContextFactory = SslContextFactory.Server()
|
||||
sslContextFactory.keyStorePath = config.keyStorePath
|
||||
sslContextFactory.setKeyStorePassword(config.keyStorePassword)
|
||||
sslContextFactory.setKeyManagerPassword(config.keyStorePassword)
|
||||
@ -118,15 +117,15 @@ class NodeWebServer(val config: WebServerConfig) {
|
||||
contextPath = "/"
|
||||
errorHandler = object : ErrorHandler() {
|
||||
@Throws(IOException::class)
|
||||
override fun writeErrorPageHead(request: HttpServletRequest, writer: Writer, code: Int, message: String) {
|
||||
writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n")
|
||||
writer.write("<title>Corda $safeLegalName : Error $code</title>\n")
|
||||
override fun writeErrorHtmlHead(request: Request?, writer: Writer?, code: Int, message: String?) {
|
||||
writer?.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n")
|
||||
writer?.write("<title>Corda $safeLegalName : Error $code</title>\n")
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun writeErrorPageMessage(request: HttpServletRequest, writer: Writer, code: Int, message: String, uri: String) {
|
||||
writer.write("<h1>Corda $safeLegalName</h1>\n")
|
||||
super.writeErrorPageMessage(request, writer, code, message, uri)
|
||||
override fun writeErrorHtmlMessage(request: Request?, writer: Writer?, code: Int, message: String?, cause: Throwable?, uri: String?) {
|
||||
writer?.write("<h1>Corda $safeLegalName</h1>\n")
|
||||
super.writeErrorHtmlMessage(request, writer, code, message, cause, uri)
|
||||
}
|
||||
}
|
||||
setAttribute("rpc", localRpc)
|
||||
|
@ -1,17 +1,18 @@
|
||||
package net.corda.webserver.servlets
|
||||
|
||||
import jakarta.servlet.http.HttpServlet
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import jakarta.ws.rs.core.HttpHeaders
|
||||
import jakarta.ws.rs.core.MediaType
|
||||
import net.corda.core.internal.extractFile
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import java.util.jar.JarInputStream
|
||||
import javax.servlet.http.HttpServlet
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import javax.ws.rs.core.HttpHeaders
|
||||
import javax.ws.rs.core.MediaType
|
||||
|
||||
/**
|
||||
* Allows the node administrator to either download full attachment zips, or individual files within those zips.
|
||||
@ -43,7 +44,7 @@ class AttachmentDownloadServlet : HttpServlet() {
|
||||
val attachment = rpc.openAttachment(hash)
|
||||
|
||||
// Don't allow case sensitive matches inside the jar, it'd just be confusing.
|
||||
val subPath = reqPath.substringAfter('/', missingDelimiterValue = "").toLowerCase()
|
||||
val subPath = reqPath.substringAfter('/', missingDelimiterValue = "").lowercase(Locale.getDefault())
|
||||
|
||||
resp.contentType = MediaType.APPLICATION_OCTET_STREAM
|
||||
if (subPath.isEmpty()) {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.corda.webserver.servlets
|
||||
|
||||
import jakarta.servlet.http.HttpServlet
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.stream.appendHTML
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
@ -7,9 +10,6 @@ import net.corda.webserver.services.WebServerPluginRegistry
|
||||
import org.glassfish.jersey.server.model.Resource
|
||||
import org.glassfish.jersey.server.model.ResourceMethod
|
||||
import java.io.IOException
|
||||
import javax.servlet.http.HttpServlet
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
/**
|
||||
* Dumps some data about the installed CorDapps.
|
||||
|
@ -1,13 +1,13 @@
|
||||
package net.corda.webserver.servlets
|
||||
|
||||
import jakarta.servlet.http.HttpServlet
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import org.apache.commons.fileupload.servlet.ServletFileUpload
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import javax.servlet.http.HttpServlet
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
/**
|
||||
* Uploads to the node via the [CordaRPCOps] uploadFile interface.
|
||||
@ -19,7 +19,7 @@ class DataUploadServlet : HttpServlet() {
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
|
||||
val isMultipart = ServletFileUpload.isMultipartContent(req)
|
||||
val isMultipart = JakartaServletFileUpload.isMultipartContent(req)
|
||||
val rpc = servletContext.getAttribute("rpc") as CordaRPCOps
|
||||
|
||||
if (!isMultipart) {
|
||||
@ -27,7 +27,7 @@ class DataUploadServlet : HttpServlet() {
|
||||
return
|
||||
}
|
||||
|
||||
val upload = ServletFileUpload()
|
||||
val upload = JakartaServletFileUpload()
|
||||
val iterator = upload.getItemIterator(req)
|
||||
val messages = ArrayList<String>()
|
||||
|
||||
@ -48,7 +48,7 @@ class DataUploadServlet : HttpServlet() {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
messages += rpc.uploadAttachment(item.openStream()).toString()
|
||||
messages += rpc.uploadAttachment(item.inputStream).toString()
|
||||
} catch (e: RuntimeException) {
|
||||
reportError(e.toString())
|
||||
continue
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.webserver.servlets
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import javax.ws.rs.ext.ContextResolver
|
||||
import javax.ws.rs.ext.Provider
|
||||
import jakarta.ws.rs.ext.ContextResolver
|
||||
import jakarta.ws.rs.ext.Provider
|
||||
|
||||
/**
|
||||
* Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.corda.webserver.servlets
|
||||
|
||||
import jakarta.ws.rs.container.ContainerRequestContext
|
||||
import jakarta.ws.rs.container.ContainerResponseContext
|
||||
import jakarta.ws.rs.container.ContainerResponseFilter
|
||||
import jakarta.ws.rs.ext.Provider
|
||||
import java.io.IOException
|
||||
import javax.ws.rs.container.ContainerRequestContext
|
||||
import javax.ws.rs.container.ContainerResponseContext
|
||||
import javax.ws.rs.container.ContainerResponseFilter
|
||||
import javax.ws.rs.ext.Provider
|
||||
|
||||
/**
|
||||
* This adds headers needed for cross site scripting on API clients.
|
||||
|
@ -2,9 +2,8 @@
|
||||
* This build.gradle exists to publish our capsule (executable fat jar) to maven. It cannot be placed in the
|
||||
* webserver project because the bintray plugin cannot publish two modules from one project.
|
||||
*/
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'us.kirchmeier.capsule'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda node web server capsule'
|
||||
|
||||
@ -24,29 +23,29 @@ capsule {
|
||||
version capsule_version
|
||||
}
|
||||
|
||||
task buildWebserverJar(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
|
||||
configurations.runtimeOnly.canBeResolved = true
|
||||
tasks.register('buildWebserverJar', FatCapsule) {
|
||||
dependsOn project(':node').tasks.jar
|
||||
applicationClass 'net.corda.webserver.WebServer'
|
||||
archiveBaseName = 'corda-testserver'
|
||||
archiveVersion = corda_release_version
|
||||
archiveClassifier = jdkClassifier
|
||||
archiveName = archiveFileName.get()
|
||||
applicationSource = files(
|
||||
project(':testing:testserver').configurations.runtimeClasspath,
|
||||
project(':testing:testserver').tasks.jar,
|
||||
project(':testing:testserver').sourceSets.main.java.outputDir.toString() + '/CordaWebserverCaplet.class',
|
||||
project(':testing:testserver').sourceSets.main.java.outputDir.toString() + '/CordaWebserverCaplet$1.class',
|
||||
project(':node').buildDir.toString() + '/resources/main/corda-reference.conf',
|
||||
"$rootDir/config/dev/log4j2.xml",
|
||||
project(':node:capsule').projectDir.toString() + '/NOTICE' // Copy CDDL notice
|
||||
project(':testing:testserver').configurations.runtimeClasspath,
|
||||
project(':testing:testserver').tasks.jar,
|
||||
project(':testing:testserver').sourceSets.main.java.outputDir.toString() + '/CordaWebserverCaplet.class',
|
||||
project(':testing:testserver').sourceSets.main.java.outputDir.toString() + '/CordaWebserverCaplet$1.class',
|
||||
project(':node').buildDir.toString() + '/resources/main/corda-reference.conf',
|
||||
"$rootDir/config/dev/log4j2.xml",
|
||||
project(':node:capsule').projectDir.toString() + '/NOTICE' // Copy CDDL notice
|
||||
)
|
||||
from configurations.capsuleRuntime.files.collect { zipTree(it) }
|
||||
|
||||
capsuleManifest {
|
||||
applicationVersion = corda_release_version
|
||||
javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar"] : ["quasar-core-${quasar_version}.jar"]
|
||||
javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=m"] : ["quasar-core-${quasar_version}.jar=m"]
|
||||
systemProperties['visualvm.display.name'] = 'Corda Webserver'
|
||||
minJavaVersion = '1.8.0'
|
||||
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
||||
minJavaVersion = '17.0'
|
||||
caplets = ['CordaWebserverCaplet']
|
||||
|
||||
// JVM configuration:
|
||||
@ -54,25 +53,22 @@ task buildWebserverJar(type: FatCapsule, dependsOn: project(':node').tasks.jar)
|
||||
// - Switch to the G1 GC which is going to be the default in Java 9 and gives low pause times/string dedup.
|
||||
//
|
||||
// If you change these flags, please also update Driver.kt
|
||||
jvmArgs = ['-Xmx200m', '-XX:+UseG1GC']
|
||||
}
|
||||
|
||||
manifest {
|
||||
if (JavaVersion.current() == JavaVersion.VERSION_11) {
|
||||
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver java.base/java.lang')
|
||||
}
|
||||
jvmArgs = ['-Xmx200m']
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives buildWebserverJar
|
||||
runtimeArtifacts buildWebserverJar
|
||||
publish buildWebserverJar {
|
||||
classifier ''
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
disableDefaultJar = true
|
||||
name 'corda-testserver'
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId 'corda-testserver'
|
||||
artifact(buildWebserverJar) {
|
||||
classifier ''
|
||||
}
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user