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:
rick.parker
2024-08-13 17:26:41 +01:00
759 changed files with 14374 additions and 12301 deletions

View File

@ -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

View File

@ -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

View 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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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")

View File

@ -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())
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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?

View File

@ -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

View File

@ -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) }

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -1,5 +1,5 @@
@file:JvmName("TestUtils")
@file:Suppress("TooGenericExceptionCaught", "MagicNumber", "ComplexMethod", "LongParameterList")
@file:Suppress("MagicNumber", "ComplexMethod", "LongParameterList")
package net.corda.testing.core

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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)) }
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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))
}
}
}

View File

@ -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()
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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.
*

View File

@ -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
}
}

View File

@ -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 }
}
}
}

View File

@ -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.
*/

View File

@ -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 }

View File

@ -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)
}
}

View File

@ -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

View File

@ -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" }
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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())
}
}

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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")

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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>

View File

@ -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
}
}
}

View File

@ -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>

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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())
}

View File

@ -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()}")
}
}
}

View File

@ -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 {

View File

@ -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"

View File

@ -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) {

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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
}
}
}

View File

@ -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.

View File

@ -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 {

View File

@ -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}")

View File

@ -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

View File

@ -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.

View File

@ -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
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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()) {

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
}
}
}