mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
ENT-11676: Support for testing backwards compatible transactions in the node driver (#7704)
* ENT-11676: Support for testing backwards compatible transactions in the node driver * Introduction of a new way to reference CorDapps for the node driver: `TestCordapp.of(URI)` * New `TestCordapp.asSigned()` method which creates a copy of the CorDapp jar but signed by a dev key. * Added `NodeParameters.legacyContracts` for specifying legacy contract CorDapps for the node `TransactionBuilderDriverTest` has been updated to use these new APIs. * ENT-11676: Support for testing backwards compatible transactions in the node driver * Introduction of a new way to reference CorDapps for the node driver: `TestCordapp.of(URI)` * New `TestCordapp.asSigned()` method which creates a copy of the CorDapp jar but signed by a dev key. * Added `NodeParameters.legacyContracts` for specifying legacy contract CorDapps for the node `TransactionBuilderDriverTest` has been updated to use these new APIs. * ENT-11676: Added removed api and fixed alias issue. --------- Co-authored-by: Adel El-Beik <adel.el-beik@r3.com>
This commit is contained in:
parent
0f713aaa44
commit
18e5f7d68f
@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.TransactionState
|
import net.corda.core.contracts.TransactionState
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.internal.copyToDirectory
|
|
||||||
import net.corda.core.internal.hash
|
import net.corda.core.internal.hash
|
||||||
import net.corda.core.internal.mapToSet
|
import net.corda.core.internal.mapToSet
|
||||||
import net.corda.core.internal.toPath
|
import net.corda.core.internal.toPath
|
||||||
@ -23,30 +22,26 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow
|
|||||||
import net.corda.finance.issuedBy
|
import net.corda.finance.issuedBy
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
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 net.corda.testing.core.internal.JarSignatureTestUtils.unsignJar
|
||||||
import net.corda.testing.driver.NodeHandle
|
import net.corda.testing.driver.NodeHandle
|
||||||
import net.corda.testing.driver.NodeParameters
|
import net.corda.testing.driver.NodeParameters
|
||||||
|
import net.corda.testing.node.TestCordapp
|
||||||
import net.corda.testing.node.internal.DriverDSLImpl
|
import net.corda.testing.node.internal.DriverDSLImpl
|
||||||
import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP
|
import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP
|
||||||
|
import net.corda.testing.node.internal.TestCordappInternal
|
||||||
|
import net.corda.testing.node.internal.UriTestCordapp
|
||||||
import net.corda.testing.node.internal.enclosedCordapp
|
import net.corda.testing.node.internal.enclosedCordapp
|
||||||
import net.corda.testing.node.internal.internalDriver
|
import net.corda.testing.node.internal.internalDriver
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy
|
import org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import kotlin.io.path.Path
|
|
||||||
import kotlin.io.path.absolutePathString
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.io.path.copyTo
|
import kotlin.io.path.copyTo
|
||||||
import kotlin.io.path.createDirectories
|
import kotlin.io.path.createDirectories
|
||||||
import kotlin.io.path.div
|
|
||||||
import kotlin.io.path.inputStream
|
import kotlin.io.path.inputStream
|
||||||
import kotlin.io.path.isRegularFile
|
import kotlin.io.path.isRegularFile
|
||||||
import kotlin.io.path.moveTo
|
import kotlin.io.path.moveTo
|
||||||
@ -57,30 +52,16 @@ class TransactionBuilderDriverTest {
|
|||||||
val legacyFinanceContractsJar = this::class.java.getResource("/corda-finance-contracts-4.11.jar")!!.toPath()
|
val legacyFinanceContractsJar = this::class.java.getResource("/corda-finance-contracts-4.11.jar")!!.toPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Rule
|
|
||||||
@JvmField
|
|
||||||
val tempFolder = TemporaryFolder()
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun initJarSigner() {
|
|
||||||
tempFolder.root.toPath().generateKey("testAlias", "testPassword", ALICE_NAME.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun signJar(jar: Path) {
|
|
||||||
tempFolder.root.toPath().signJar(jar.absolutePathString(), "testAlias", "testPassword")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout=300_000)
|
@Test(timeout=300_000)
|
||||||
fun `adds CorDapp dependencies`() {
|
fun `adds CorDapp dependencies`() {
|
||||||
internalDriver(cordappsForAllNodes = listOf(FINANCE_WORKFLOWS_CORDAPP), startNodesInProcess = false) {
|
internalDriver(cordappsForAllNodes = listOf(FINANCE_WORKFLOWS_CORDAPP), startNodesInProcess = false) {
|
||||||
val (cordapp, dependency) = splitFinanceContractCordapp(currentFinanceContractsJar)
|
val (cordapp, dependency) = splitFinanceContractCordapp(currentFinanceContractsJar)
|
||||||
|
|
||||||
cordapp.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
cordapp.jarFile.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
||||||
dependency.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
dependency.jarFile.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
||||||
|
|
||||||
// Start the node with the CorDapp but without the dependency
|
// Start the node with the CorDapp but without the dependency
|
||||||
cordapp.copyToDirectory((baseDirectory(ALICE_NAME) / "cordapps").createDirectories())
|
val node = startNode(NodeParameters(ALICE_NAME, additionalCordapps = listOf(cordapp))).getOrThrow()
|
||||||
val node = startNode(NodeParameters(ALICE_NAME)).getOrThrow()
|
|
||||||
|
|
||||||
// First make sure the missing dependency causes an issue
|
// First make sure the missing dependency causes an issue
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
@ -88,10 +69,10 @@ class TransactionBuilderDriverTest {
|
|||||||
}.hasMessageContaining("Transaction being built has a missing attachment for class net/corda/finance/contracts/asset/")
|
}.hasMessageContaining("Transaction being built has a missing attachment for class net/corda/finance/contracts/asset/")
|
||||||
|
|
||||||
// Upload the missing dependency
|
// Upload the missing dependency
|
||||||
dependency.inputStream().use(node.rpc::uploadAttachment)
|
dependency.jarFile.inputStream().use(node.rpc::uploadAttachment)
|
||||||
|
|
||||||
val stx = createTransaction(node)
|
val stx = createTransaction(node)
|
||||||
assertThat(stx.tx.attachments).contains(cordapp.hash, dependency.hash)
|
assertThat(stx.tx.attachments).contains(cordapp.jarFile.hash, dependency.jarFile.hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,17 +84,16 @@ class TransactionBuilderDriverTest {
|
|||||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||||
) {
|
) {
|
||||||
val (legacyContracts, legacyDependency) = splitFinanceContractCordapp(legacyFinanceContractsJar)
|
val (legacyContracts, legacyDependency) = splitFinanceContractCordapp(legacyFinanceContractsJar)
|
||||||
// Re-sign the current finance contracts CorDapp with the same key as the split legacy CorDapp
|
val currentContracts = TestCordapp.of(currentFinanceContractsJar.toUri()).asSigned() as TestCordappInternal
|
||||||
val currentContracts = currentFinanceContractsJar.copyTo(Path("${currentFinanceContractsJar.toString().substringBeforeLast(".")}-RESIGNED.jar"), overwrite = true)
|
|
||||||
currentContracts.unsignJar()
|
|
||||||
signJar(currentContracts)
|
|
||||||
|
|
||||||
currentContracts.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
currentContracts.jarFile.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
||||||
|
|
||||||
// Start the node with the legacy CorDapp but without the dependency
|
// Start the node with the legacy CorDapp but without the dependency
|
||||||
legacyContracts.copyToDirectory((baseDirectory(ALICE_NAME) / "legacy-contracts").createDirectories())
|
val node = startNode(NodeParameters(
|
||||||
currentContracts.copyToDirectory((baseDirectory(ALICE_NAME) / "cordapps").createDirectories())
|
ALICE_NAME,
|
||||||
val node = startNode(NodeParameters(ALICE_NAME)).getOrThrow()
|
additionalCordapps = listOf(currentContracts),
|
||||||
|
legacyContracts = listOf(legacyContracts)
|
||||||
|
)).getOrThrow()
|
||||||
|
|
||||||
// First make sure the missing dependency causes an issue
|
// First make sure the missing dependency causes an issue
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
@ -121,10 +101,10 @@ class TransactionBuilderDriverTest {
|
|||||||
}.hasMessageContaining("Transaction being built has a missing legacy attachment for class net/corda/finance/contracts/asset/")
|
}.hasMessageContaining("Transaction being built has a missing legacy attachment for class net/corda/finance/contracts/asset/")
|
||||||
|
|
||||||
// Upload the missing dependency
|
// Upload the missing dependency
|
||||||
legacyDependency.inputStream().use(node.rpc::uploadAttachment)
|
legacyDependency.jarFile.inputStream().use(node.rpc::uploadAttachment)
|
||||||
|
|
||||||
val stx = createTransaction(node)
|
val stx = createTransaction(node)
|
||||||
assertThat(stx.tx.legacyAttachments).contains(legacyContracts.hash, legacyDependency.hash)
|
assertThat(stx.tx.legacyAttachments).contains(legacyContracts.jarFile.hash, legacyDependency.jarFile.hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,16 +119,16 @@ class TransactionBuilderDriverTest {
|
|||||||
val (currentCashContract, currentCpContract) = splitJar(currentFinanceContractsJar) { "CommercialPaper" in it.absolutePathString() }
|
val (currentCashContract, currentCpContract) = splitJar(currentFinanceContractsJar) { "CommercialPaper" in it.absolutePathString() }
|
||||||
val (legacyCashContract, _) = splitJar(legacyFinanceContractsJar) { "CommercialPaper" in it.absolutePathString() }
|
val (legacyCashContract, _) = splitJar(legacyFinanceContractsJar) { "CommercialPaper" in it.absolutePathString() }
|
||||||
|
|
||||||
currentCashContract.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
currentCashContract.jarFile.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
||||||
currentCpContract.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
currentCpContract.jarFile.inputStream().use(defaultNotaryNode.getOrThrow().rpc::uploadAttachment)
|
||||||
|
|
||||||
// The node has the legacy CommericalPaper contract missing
|
// The node has the legacy CommericalPaper contract missing
|
||||||
val cordappsDir = (baseDirectory(ALICE_NAME) / "cordapps").createDirectories()
|
val node = startNode(NodeParameters(
|
||||||
currentCashContract.copyToDirectory(cordappsDir)
|
ALICE_NAME,
|
||||||
currentCpContract.copyToDirectory(cordappsDir)
|
additionalCordapps = listOf(currentCashContract, currentCpContract),
|
||||||
legacyCashContract.copyToDirectory((baseDirectory(ALICE_NAME) / "legacy-contracts").createDirectories())
|
legacyContracts = listOf(legacyCashContract)
|
||||||
|
)).getOrThrow()
|
||||||
|
|
||||||
val node = startNode(NodeParameters(ALICE_NAME)).getOrThrow()
|
|
||||||
assertThatThrownBy { node.rpc.startFlow(::TwoContractTransactionFlow).returnValue.getOrThrow() }
|
assertThatThrownBy { node.rpc.startFlow(::TwoContractTransactionFlow).returnValue.getOrThrow() }
|
||||||
.hasMessageContaining("Transaction being built has a missing legacy attachment")
|
.hasMessageContaining("Transaction being built has a missing legacy attachment")
|
||||||
.hasMessageContaining("CommercialPaper")
|
.hasMessageContaining("CommercialPaper")
|
||||||
@ -158,11 +138,11 @@ class TransactionBuilderDriverTest {
|
|||||||
/**
|
/**
|
||||||
* Split the given finance contracts jar into two such that the second jar becomes a dependency to the first.
|
* Split the given finance contracts jar into two such that the second jar becomes a dependency to the first.
|
||||||
*/
|
*/
|
||||||
private fun DriverDSLImpl.splitFinanceContractCordapp(contractsJar: Path): Pair<Path, Path> {
|
private fun DriverDSLImpl.splitFinanceContractCordapp(contractsJar: Path): Pair<UriTestCordapp, UriTestCordapp> {
|
||||||
return splitJar(contractsJar) { it.absolutePathString() == "/net/corda/finance/contracts/asset/CashUtilities.class" }
|
return splitJar(contractsJar) { it.absolutePathString() == "/net/corda/finance/contracts/asset/CashUtilities.class" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DriverDSLImpl.splitJar(path: Path, move: (Path) -> Boolean): Pair<Path, Path> {
|
private fun DriverDSLImpl.splitJar(path: Path, move: (Path) -> Boolean): Pair<UriTestCordapp, UriTestCordapp> {
|
||||||
val jar1 = Files.createTempFile(driverDirectory, "jar1-", ".jar")
|
val jar1 = Files.createTempFile(driverDirectory, "jar1-", ".jar")
|
||||||
val jar2 = Files.createTempFile(driverDirectory, "jar2-", ".jar")
|
val jar2 = Files.createTempFile(driverDirectory, "jar2-", ".jar")
|
||||||
|
|
||||||
@ -181,10 +161,10 @@ class TransactionBuilderDriverTest {
|
|||||||
}
|
}
|
||||||
jar1.unsignJar()
|
jar1.unsignJar()
|
||||||
|
|
||||||
signJar(jar1)
|
return Pair(
|
||||||
signJar(jar2)
|
TestCordapp.of(jar1.toUri()).asSigned() as UriTestCordapp,
|
||||||
|
TestCordapp.of(jar2.toUri()).asSigned() as UriTestCordapp
|
||||||
return Pair(jar1, jar2)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DriverDSLImpl.createTransaction(node: NodeHandle): SignedTransaction {
|
private fun DriverDSLImpl.createTransaction(node: NodeHandle): SignedTransaction {
|
||||||
|
@ -12,6 +12,7 @@ import java.nio.file.Path
|
|||||||
import java.util.jar.Attributes
|
import java.util.jar.Attributes
|
||||||
import java.util.jar.JarOutputStream
|
import java.util.jar.JarOutputStream
|
||||||
import java.util.jar.Manifest
|
import java.util.jar.Manifest
|
||||||
|
import kotlin.io.path.exists
|
||||||
import kotlin.io.path.fileSize
|
import kotlin.io.path.fileSize
|
||||||
import kotlin.io.path.inputStream
|
import kotlin.io.path.inputStream
|
||||||
import kotlin.io.path.outputStream
|
import kotlin.io.path.outputStream
|
||||||
@ -36,9 +37,10 @@ inline fun <T> Path.useZipFile(block: (FileSystem) -> T): T {
|
|||||||
return FileSystems.newFileSystem(this).use(block)
|
return FileSystems.newFileSystem(this).use(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <T> Path.modifyJarManifest(block: (Manifest) -> T): T {
|
inline fun <T> Path.modifyJarManifest(block: (Manifest) -> T): T? {
|
||||||
return useZipFile { zipFs ->
|
return useZipFile { zipFs ->
|
||||||
val manifestFile = zipFs.getPath("META-INF", "MANIFEST.MF")
|
val manifestFile = zipFs.getPath("META-INF", "MANIFEST.MF")
|
||||||
|
if (!manifestFile.exists()) return null
|
||||||
val manifest = manifestFile.inputStream().use(::Manifest)
|
val manifest = manifestFile.inputStream().use(::Manifest)
|
||||||
val result = block(manifest)
|
val result = block(manifest)
|
||||||
manifestFile.outputStream().use(manifest::write)
|
manifestFile.outputStream().use(manifest::write)
|
||||||
|
@ -21,6 +21,7 @@ import java.util.jar.JarOutputStream
|
|||||||
import java.util.jar.Manifest
|
import java.util.jar.Manifest
|
||||||
import kotlin.io.path.deleteExisting
|
import kotlin.io.path.deleteExisting
|
||||||
import kotlin.io.path.div
|
import kotlin.io.path.div
|
||||||
|
import kotlin.io.path.exists
|
||||||
import kotlin.io.path.listDirectoryEntries
|
import kotlin.io.path.listDirectoryEntries
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ object JarSignatureTestUtils {
|
|||||||
fun Path.unsignJar() {
|
fun Path.unsignJar() {
|
||||||
// Remove the signatures
|
// Remove the signatures
|
||||||
useZipFile { zipFs ->
|
useZipFile { zipFs ->
|
||||||
zipFs.getPath("META-INF").listDirectoryEntries("*.{SF,DSA,RSA,EC}").forEach(Path::deleteExisting)
|
zipFs.getPath("META-INF").takeIf { it.exists() }?.listDirectoryEntries("*.{SF,DSA,RSA,EC}")?.forEach(Path::deleteExisting)
|
||||||
}
|
}
|
||||||
// Remove all the hash information of the jar contents
|
// Remove all the hash information of the jar contents
|
||||||
modifyJarManifest { manifest ->
|
modifyJarManifest { manifest ->
|
||||||
|
@ -25,7 +25,7 @@ import net.corda.testing.node.User
|
|||||||
* log level argument.
|
* 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.
|
* @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(
|
data class NodeParameters(
|
||||||
val providedName: CordaX500Name? = null,
|
val providedName: CordaX500Name? = null,
|
||||||
val rpcUsers: List<User> = emptyList(),
|
val rpcUsers: List<User> = emptyList(),
|
||||||
@ -37,7 +37,8 @@ data class NodeParameters(
|
|||||||
val flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>> = emptyMap(),
|
val flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>> = emptyMap(),
|
||||||
val logLevelOverride: String? = null,
|
val logLevelOverride: String? = null,
|
||||||
val rpcAddress: NetworkHostAndPort? = 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
|
* 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 withAdditionalCordapps(additionalCordapps: Set<TestCordapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
|
||||||
fun withFlowOverrides(flowOverrides: Map<Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>): NodeParameters = copy(flowOverrides = flowOverrides)
|
fun withFlowOverrides(flowOverrides: Map<Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>): NodeParameters = copy(flowOverrides = flowOverrides)
|
||||||
fun withLogLevelOverride(logLevelOverride: String?): NodeParameters = copy(logLevelOverride = logLevelOverride)
|
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(
|
constructor(
|
||||||
providedName: CordaX500Name?,
|
providedName: CordaX500Name?,
|
||||||
@ -221,4 +225,58 @@ data class NodeParameters(
|
|||||||
logLevelOverride = logLevelOverride,
|
logLevelOverride = logLevelOverride,
|
||||||
rpcAddress = rpcAddress,
|
rpcAddress = rpcAddress,
|
||||||
systemProperties = systemProperties)
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@ package net.corda.testing.node
|
|||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.NodeParameters
|
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]
|
* 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. */
|
/** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */
|
||||||
abstract fun withConfig(config: Map<String, Any>): TestCordapp
|
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 {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Scans the current classpath to find the CorDapp that contains the given package. All the CorDapp's metdata present in its
|
* 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.
|
* @param scanPackage The package name used to find the CorDapp. This does not need to be the root package of the CorDapp.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,6 @@ import net.corda.core.node.services.AttachmentFixup
|
|||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
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 java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.nio.file.attribute.FileTime
|
import java.nio.file.attribute.FileTime
|
||||||
@ -49,6 +46,8 @@ data class CustomCordapp(
|
|||||||
|
|
||||||
override fun withOnlyJarContents(): CustomCordapp = CustomCordapp(packages = packages, classes = classes, fixups = fixups)
|
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 =
|
fun signed(keyStorePath: Path? = null, numberOfSignatures: Int = 1, keyAlgorithm: String = "RSA"): CustomCordapp =
|
||||||
copy(signingInfo = SigningInfo(keyStorePath, numberOfSignatures, keyAlgorithm))
|
copy(signingInfo = SigningInfo(keyStorePath, numberOfSignatures, keyAlgorithm))
|
||||||
|
|
||||||
@ -114,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 {
|
private fun createTestManifest(name: String, versionId: Int, targetPlatformVersion: Int): Manifest {
|
||||||
val manifest = Manifest()
|
val manifest = Manifest()
|
||||||
|
|
||||||
@ -160,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 {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
private val epochFileTime = FileTime.from(Instant.EPOCH)
|
private val epochFileTime = FileTime.from(Instant.EPOCH)
|
||||||
private val cordappsDirectory: Path
|
private val cordappsDirectory: Path
|
||||||
private val defaultJarSignerDirectory: Path
|
|
||||||
private val whitespace = "\\s++".toRegex()
|
private val whitespace = "\\s++".toRegex()
|
||||||
private val cache = ConcurrentHashMap<CustomCordapp, Path>()
|
private val cache = ConcurrentHashMap<CustomCordapp, Path>()
|
||||||
|
|
||||||
@ -174,7 +155,6 @@ data class CustomCordapp(
|
|||||||
val buildDir = Paths.get("build").toAbsolutePath()
|
val buildDir = Paths.get("build").toAbsolutePath()
|
||||||
val timeDirName = getTimestampAsDirectoryName()
|
val timeDirName = getTimestampAsDirectoryName()
|
||||||
cordappsDirectory = buildDir / "generated-custom-cordapps" / timeDirName
|
cordappsDirectory = buildDir / "generated-custom-cordapps" / timeDirName
|
||||||
defaultJarSignerDirectory = buildDir / "jar-signer" / timeDirName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getJarFile(cordapp: CustomCordapp): Path {
|
fun getJarFile(cordapp: CustomCordapp): Path {
|
||||||
@ -187,7 +167,9 @@ data class CustomCordapp(
|
|||||||
} else if (it.packages.isNotEmpty() || it.classes.isNotEmpty()) {
|
} else if (it.packages.isNotEmpty() || it.classes.isNotEmpty()) {
|
||||||
it.packageAsJar(jarFile)
|
it.packageAsJar(jarFile)
|
||||||
}
|
}
|
||||||
it.signJar(jarFile)
|
if (it.signingInfo != null) {
|
||||||
|
TestCordappSigner.signJar(jarFile, it.signingInfo.keyStorePath, it.signingInfo.signatureCount, it.signingInfo.algorithm)
|
||||||
|
}
|
||||||
logger.debug { "$it packaged into $jarFile" }
|
logger.debug { "$it packaged into $jarFile" }
|
||||||
jarFile
|
jarFile
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import net.corda.core.internal.concurrent.fork
|
|||||||
import net.corda.core.internal.concurrent.map
|
import net.corda.core.internal.concurrent.map
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.concurrent.transpose
|
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_LICENCE
|
||||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
|
||||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VENDOR
|
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VENDOR
|
||||||
@ -55,6 +56,7 @@ import net.corda.node.internal.DataSourceFactory
|
|||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.NodeWithInfo
|
import net.corda.node.internal.NodeWithInfo
|
||||||
import net.corda.node.internal.clientSslOptionsCompatibleWith
|
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.Permissions
|
||||||
import net.corda.node.services.config.ConfigHelper
|
import net.corda.node.services.config.ConfigHelper
|
||||||
import net.corda.node.services.config.FlowOverride
|
import net.corda.node.services.config.FlowOverride
|
||||||
@ -716,6 +718,11 @@ class DriverDSLImpl(
|
|||||||
extraCustomCordapps + (cordappsForAllNodes ?: emptySet())
|
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 nodeFuture = if (parameters.startInSameProcess ?: startNodesInProcess) {
|
||||||
val nodeAndThreadFuture = startInProcessNode(executorService, config, allowHibernateToManageAppSchema)
|
val nodeAndThreadFuture = startInProcessNode(executorService, config, allowHibernateToManageAppSchema)
|
||||||
shutdownManager.registerShutdown(
|
shutdownManager.registerShutdown(
|
||||||
|
@ -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.
|
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the flows as well.
|
||||||
*/
|
*/
|
||||||
@JvmField
|
@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
|
* 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.
|
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the contract classes as well.
|
||||||
*/
|
*/
|
||||||
@JvmField
|
@JvmField
|
||||||
val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.workflows")
|
val FINANCE_WORKFLOWS_CORDAPP: ScanPackageTestCordapp = findCordapp("net.corda.finance.workflows")
|
||||||
|
|
||||||
@JvmField
|
@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
|
* *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
|
* 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. */
|
/** Create a *custom* CorDapp which just contains the enclosed classes of the receiver class. */
|
||||||
fun Any.enclosedCordapp(): CustomCordapp {
|
fun Any.enclosedCordapp(): CustomCordapp {
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.testing.node.internal
|
|||||||
|
|
||||||
import io.github.classgraph.ClassGraph
|
import io.github.classgraph.ClassGraph
|
||||||
import net.corda.core.internal.attributes
|
import net.corda.core.internal.attributes
|
||||||
|
import net.corda.core.internal.mapToSet
|
||||||
import net.corda.core.internal.pooledScan
|
import net.corda.core.internal.pooledScan
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.testing.node.TestCordapp
|
import net.corda.testing.node.TestCordapp
|
||||||
@ -23,39 +24,43 @@ import kotlin.io.path.useDirectoryEntries
|
|||||||
* the [scanPackage] may reference a gradle CorDapp project on the local system. In this scenerio the project's "jar" task is executed to
|
* 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.
|
* 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() {
|
data class ScanPackageTestCordapp(val scanPackage: String,
|
||||||
override fun withConfig(config: Map<String, Any>): TestCordappImpl = copy(config = config)
|
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
|
override fun withOnlyJarContents(): ScanPackageTestCordapp = copy(config = emptyMap(), signed = false)
|
||||||
get() {
|
|
||||||
val jars = findJars(scanPackage)
|
override val jarFile: Path by lazy {
|
||||||
when (jars.size) {
|
val jars = findJars()
|
||||||
0 -> throw IllegalArgumentException("There are no CorDapps containing the package $scanPackage on the classpath. Make sure " +
|
val jar = when (jars.size) {
|
||||||
"the package name is correct and that the CorDapp is added as a gradle dependency.")
|
0 -> throw IllegalArgumentException("There are no CorDapps containing the package $scanPackage on the classpath. Make sure " +
|
||||||
1 -> return jars.first()
|
"the package name is correct and that the CorDapp is added as a gradle dependency.")
|
||||||
else -> throw IllegalArgumentException("There is more than one CorDapp containing the package $scanPackage on the classpath " +
|
1 -> jars.first()
|
||||||
"$jars. Specify a package name which is unique to the CorDapp.")
|
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 {
|
companion object {
|
||||||
private val packageToRootPaths = ConcurrentHashMap<String, Set<Path>>()
|
private val packageToRootPaths = ConcurrentHashMap<String, Set<Path>>()
|
||||||
private val projectRootToBuiltJar = ConcurrentHashMap<Path, Path>()
|
private val projectRootToBuiltJar = ConcurrentHashMap<Path, Path>()
|
||||||
private val log = contextLogger()
|
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> {
|
private fun findRootPaths(scanPackage: String): Set<Path> {
|
||||||
return packageToRootPaths.computeIfAbsent(scanPackage) {
|
return packageToRootPaths.computeIfAbsent(scanPackage) {
|
||||||
val classGraph = ClassGraph().acceptPaths(scanPackage.replace('.', '/'))
|
val classGraph = ClassGraph().acceptPaths(scanPackage.replace('.', '/'))
|
@ -0,0 +1,50 @@
|
|||||||
|
package net.corda.testing.node.internal
|
||||||
|
|
||||||
|
import net.corda.core.internal.JarSignatureCollector
|
||||||
|
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 java.util.jar.JarInputStream
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
import kotlin.io.path.copyTo
|
||||||
|
import kotlin.io.path.inputStream
|
||||||
|
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) {
|
||||||
|
println("On signer $i")
|
||||||
|
// 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)
|
||||||
|
println("Number of actual signers: ${JarInputStream(jar.inputStream()).use { JarSignatureCollector.collectSigners(it).size }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user