Create a unit test for serialization whitelists via driver.

This commit is contained in:
Chris Rankin 2020-02-05 10:46:42 +00:00
parent dc92786d17
commit d89ce6608a
15 changed files with 212 additions and 32 deletions

View File

@ -36,6 +36,7 @@ fun <T: Any> getNamesOfClassesImplementing(classloader: ClassLoader, clazz: Clas
return ClassGraph().overrideClassLoaders(classloader)
.enableURLScheme(attachmentScheme)
.ignoreParentClassLoaders()
.disableDirScanning()
.enableClassInfo()
.pooledScan()
.use { result ->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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