mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
Create a unit test for serialization whitelists via driver.
This commit is contained in:
parent
dc92786d17
commit
d89ce6608a
@ -36,6 +36,7 @@ fun <T: Any> getNamesOfClassesImplementing(classloader: ClassLoader, clazz: Clas
|
||||
return ClassGraph().overrideClassLoaders(classloader)
|
||||
.enableURLScheme(attachmentScheme)
|
||||
.ignoreParentClassLoaders()
|
||||
.disableDirScanning()
|
||||
.enableClassInfo()
|
||||
.pooledScan()
|
||||
.use { result ->
|
||||
|
@ -0,0 +1,38 @@
|
||||
package net.corda.contracts.serialization.whitelist
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
|
||||
class WhitelistContract : Contract {
|
||||
companion object {
|
||||
const val MAX_VALUE = 2000L
|
||||
}
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
val states = tx.outputsOfType<State>()
|
||||
require(states.isNotEmpty()) {
|
||||
"Requires at least one data state"
|
||||
}
|
||||
|
||||
states.forEach {
|
||||
require(it.whitelistData in WhitelistData(0)..WhitelistData(MAX_VALUE)) {
|
||||
"WhitelistData $it exceeds maximum value!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
|
||||
class State(val owner: AbstractParty, val whitelistData: WhitelistData) : ContractState {
|
||||
override val participants: List<AbstractParty> = listOf(owner)
|
||||
|
||||
@Override
|
||||
override fun toString(): String {
|
||||
return whitelistData.toString()
|
||||
}
|
||||
}
|
||||
|
||||
class Operate : CommandData
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package net.corda.contracts.serialization.whitelist
|
||||
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
|
||||
data class WhitelistData(val value: Long) : Comparable<WhitelistData> {
|
||||
override fun compareTo(other: WhitelistData): Int {
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
|
||||
override fun toString(): String = "$value things"
|
||||
}
|
||||
|
||||
class Whitelist : SerializationWhitelist {
|
||||
override val whitelist = listOf(WhitelistData::class.java)
|
||||
}
|
@ -6,11 +6,9 @@ import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class SandboxAttachmentFlow(private val command: CommandData) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
|
@ -6,11 +6,9 @@ import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.TypeOnlyCommandData
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class NonDeterministicFlow(private val trouble: TypeOnlyCommandData) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
|
@ -6,12 +6,10 @@ import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class DeterministicCryptoFlow(
|
||||
private val command: CommandData,
|
||||
|
@ -7,11 +7,9 @@ import net.corda.contracts.djvm.whitelist.WhitelistData
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class DeterministicWhitelistFlow(private val data: WhitelistData) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
|
@ -7,11 +7,9 @@ import net.corda.contracts.fixup.dependent.DependentData
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class CordappFixupFlow(private val data: DependentData) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
|
@ -7,11 +7,9 @@ import net.corda.contracts.serialization.custom.CustomSerializerContract.Purchas
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class CustomSerializerFlow(
|
||||
private val purchase: Currantsy
|
||||
|
@ -7,11 +7,9 @@ import net.corda.contracts.serialization.missing.MissingSerializerContract.Opera
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class MissingSerializerBuilderFlow(private val value: Long) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
|
@ -12,14 +12,12 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignableData
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.internal.createComponentGroups
|
||||
import net.corda.core.internal.requiredContractClassName
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class MissingSerializerFlow(private val value: Long) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
|
@ -0,0 +1,26 @@
|
||||
package net.corda.flows.serialization.whitelist
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.serialization.whitelist.WhitelistContract
|
||||
import net.corda.contracts.serialization.whitelist.WhitelistContract.State
|
||||
import net.corda.contracts.serialization.whitelist.WhitelistData
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@StartableByRPC
|
||||
class WhitelistFlow(private val data: WhitelistData) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
override fun call(): SecureHash {
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
val stx = serviceHub.signInitialTransaction(
|
||||
TransactionBuilder(notary)
|
||||
.addOutputState(State(ourIdentity, data))
|
||||
.addCommand(Command(WhitelistContract.Operate(), ourIdentity.owningKey))
|
||||
)
|
||||
stx.verify(serviceHub, checkSufficientSignatures = false)
|
||||
return stx.id
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.contracts.serialization.whitelist.WhitelistData
|
||||
import net.corda.core.contracts.TransactionVerificationException.ContractRejection
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.serialization.whitelist.WhitelistFlow
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@Suppress("FunctionName")
|
||||
class ContractWithSerializationWhitelistTest {
|
||||
companion object {
|
||||
const val DATA = 123456L
|
||||
|
||||
@JvmField
|
||||
val contractCordapp = cordappWithPackages("net.corda.contracts.serialization.whitelist").signed()
|
||||
|
||||
@JvmField
|
||||
val workflowCordapp = cordappWithPackages("net.corda.flows.serialization.whitelist").signed()
|
||||
|
||||
fun parametersFor(runInProcess: Boolean): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
cordappsForAllNodes = listOf(contractCordapp, workflowCordapp)
|
||||
)
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun checkData() {
|
||||
assertNotCordaSerializable<WhitelistData>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test serialization whitelist out-of-process`() {
|
||||
val user = User("u", "p", setOf(Permissions.all()))
|
||||
driver(parametersFor(runInProcess = false)) {
|
||||
val badData = WhitelistData(DATA)
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val ex = assertFailsWith<ContractRejection> {
|
||||
CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::WhitelistFlow, badData)
|
||||
.returnValue
|
||||
.getOrThrow()
|
||||
}
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageContaining("WhitelistData $badData exceeds maximum value!")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test serialization whitelist in-process`() {
|
||||
val user = User("u", "p", setOf(Permissions.all()))
|
||||
driver(parametersFor(runInProcess = true)) {
|
||||
val badData = WhitelistData(DATA)
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val ex = assertFailsWith<ContractRejection> {
|
||||
CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::WhitelistFlow, badData)
|
||||
.returnValue
|
||||
.getOrThrow()
|
||||
}
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageContaining("WhitelistData $badData exceeds maximum value!")
|
||||
}
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun `test serialization whitelist in-process`() {
|
||||
// assertFailsWith<NotCordappWhitelist> {
|
||||
// driver(parametersFor(runInProcess = true)) {}
|
||||
// }
|
||||
// }
|
||||
}
|
@ -219,7 +219,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
// present in the CorDapp.
|
||||
val result = scanResult.getClassesWithSuperclass(NotaryService::class) +
|
||||
scanResult.getClassesWithSuperclass(SinglePartyNotaryService::class)
|
||||
if(!result.isEmpty()) {
|
||||
if (result.isNotEmpty()) {
|
||||
logger.info("Found notary service CorDapp implementations: " + result.joinToString(", "))
|
||||
}
|
||||
return result.firstOrNull()
|
||||
@ -270,10 +270,26 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
private fun findWhitelists(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
||||
val whitelists = URLClassLoader(arrayOf(cordappJarPath.url)).use {
|
||||
ServiceLoader.load(SerializationWhitelist::class.java, it).toList()
|
||||
}.filter {
|
||||
it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix) && it.javaClass.location == cordappJarPath.url
|
||||
}
|
||||
return whitelists.filter {
|
||||
it.javaClass.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
|
||||
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
||||
|
||||
whitelists.filterNot {
|
||||
it.javaClass.location == cordappJarPath.url
|
||||
}.apply {
|
||||
if (isNotEmpty()) {
|
||||
throw NotCordappWhitelist("Whitelists ${showClasses(this)} not found within ${cordappJarPath.url}")
|
||||
}
|
||||
}
|
||||
return whitelists + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
||||
}
|
||||
|
||||
private fun showClasses(items: Iterable<Any>): String {
|
||||
return items.map {
|
||||
it::class.java
|
||||
}.map {
|
||||
"${it.name} in ${it.protectionDomain.codeSource.location}"
|
||||
}.toString()
|
||||
}
|
||||
|
||||
private fun findSerializers(scanResult: RestrictedScanResult): List<SerializationCustomSerializer<*, *>> {
|
||||
@ -381,6 +397,13 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
*/
|
||||
class MultipleCordappsForFlowException(message: String) : Exception(message)
|
||||
|
||||
/**
|
||||
* Thrown when a [SerializationWhitelist] is loaded from outside the CorDapp.
|
||||
* Most likely because you are testing with node-driver and "in-process" nodes.
|
||||
* Try using "out-of-process" driver nodes instead.
|
||||
*/
|
||||
class NotCordappWhitelist(message: String) : Exception(message)
|
||||
|
||||
/**
|
||||
* Thrown if an exception occurs whilst parsing version identifiers within cordapp configuration
|
||||
*/
|
||||
|
@ -142,11 +142,7 @@ class DriverDSLImpl(
|
||||
private lateinit var _notaries: CordaFuture<List<NotaryHandle>>
|
||||
override val notaryHandles: List<NotaryHandle> get() = _notaries.getOrThrow()
|
||||
|
||||
override val cordappsClassLoader: ClassLoader? = if (!startNodesInProcess) {
|
||||
createCordappsClassLoader(cordappsForAllNodes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
override val cordappsClassLoader: URLClassLoader? = createCordappsClassLoader(cordappsForAllNodes)
|
||||
|
||||
interface Waitable {
|
||||
@Throws(InterruptedException::class)
|
||||
@ -195,6 +191,7 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
cordappsClassLoader.use { _ ->
|
||||
if (waitForAllNodesToFinish) {
|
||||
state.locked {
|
||||
processes.forEach { it.waitFor() }
|
||||
@ -202,7 +199,7 @@ class DriverDSLImpl(
|
||||
}
|
||||
_shutdownManager?.shutdown()
|
||||
_executorService?.shutdownNow()
|
||||
(cordappsClassLoader as? AutoCloseable)?.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
|
||||
@ -992,7 +989,7 @@ class DriverDSLImpl(
|
||||
return config
|
||||
}
|
||||
|
||||
private fun createCordappsClassLoader(cordapps: Collection<TestCordappInternal>?): ClassLoader? {
|
||||
private fun createCordappsClassLoader(cordapps: Collection<TestCordappInternal>?): URLClassLoader? {
|
||||
if (cordapps == null || cordapps.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user