Skip to content

Commit

Permalink
V1 tests and fixes for the ContractConstraints work (corda#1739)
Browse files Browse the repository at this point in the history
* V1 tests and fixes for the ContractConstraints work

* More fixes.
  • Loading branch information
Clintonio authored and josecoll committed Sep 29, 2017
1 parent e6c2b37 commit 84a60d1
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 58 deletions.
14 changes: 1 addition & 13 deletions core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,4 @@ class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentEx
*/
// TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes)
@CordaSerializable
interface FlowLogicRef


/**
* This is just some way to track what attachments need to be in the class loader, but may later include some app
* properties loaded from the attachments. And perhaps the authenticated user for an API call?
*/
@CordaSerializable
data class AppContext(val attachments: List<SecureHash>) {
// TODO: build a real [AttachmentsClassLoader] etc
val classLoader: ClassLoader
get() = this.javaClass.classLoader
}
interface FlowLogicRef
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ class IsolatedDummyFlow {
stx.verify(serviceHub)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package net.corda.node.services

import net.corda.client.rpc.RPCException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Contract
import net.corda.core.contracts.PartyAndReference
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.transpose
Expand All @@ -16,12 +18,18 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.StartedNode
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.MockServices
import net.corda.testing.resetTestSerialization
Expand All @@ -30,6 +38,8 @@ import org.junit.Before
import org.junit.Test
import java.net.URLClassLoader
import java.nio.file.Files
import java.sql.Driver
import kotlin.test.assertFails
import kotlin.test.assertFailsWith

class AttachmentLoadingTests : TestDependencyInjectionBase() {
Expand All @@ -45,6 +55,13 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() {
val logger = loggerFor<AttachmentLoadingTests>()
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"

val bankAName = CordaX500Name("BankA", "Zurich", "CH")
val bankBName = CordaX500Name("BankB", "Zurich", "CH")
val notaryName = CordaX500Name("Notary", "Zurich", "CH")
val flowInitiatorClass =
Class.forName("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator", true, URLClassLoader(arrayOf(isolatedJAR)))
.asSubclass(FlowLogic::class.java)
}

private lateinit var services: Services
Expand Down Expand Up @@ -75,40 +92,42 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() {
@Test
fun `test that attachments retrieved over the network are not used for code`() {
driver(initialiseSerialization = false) {
val bankAName = CordaX500Name("BankA", "Zurich", "CH")
val bankBName = CordaX500Name("BankB", "Zurich", "CH")
// Copy the app jar to the first node. The second won't have it.
val path = (baseDirectory(bankAName.toString()) / "plugins").createDirectories() / "isolated.jar"
logger.info("Installing isolated jar to $path")
isolatedJAR.openStream().buffered().use { input ->
Files.newOutputStream(path).buffered().use { output ->
input.copyTo(output)
}
}

val admin = User("admin", "admin", permissions = setOf("ALL"))
val (bankA, bankB) = listOf(
startNode(providedName = bankAName, rpcUsers = listOf(admin)),
startNode(providedName = bankBName, rpcUsers = listOf(admin))
).transpose().getOrThrow() // Wait for all nodes to start up.
installIsolatedCordappTo(bankAName)
val (bankA, bankB, _) = createTwoNodesAndNotary()

val clazz =
Class.forName("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator", true, URLClassLoader(arrayOf(isolatedJAR)))
.asSubclass(FlowLogic::class.java)
assertFailsWith<UnexpectedFlowEndException>("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") {
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
}
}
}

try {
bankA.rpcClientToNode().start("admin", "admin").use { rpc ->
val proxy = rpc.proxy
val party = proxy.wellKnownPartyFromX500Name(bankBName)!!
@Test
fun `tests that if the attachment is loaded on both sides already that a flow can run`() {
driver(initialiseSerialization = false) {
installIsolatedCordappTo(bankAName)
installIsolatedCordappTo(bankBName)
val (bankA, bankB, _) = createTwoNodesAndNotary()
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
}
}

assertFailsWith<RPCException>("net.corda.client.rpc.RPCException: net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") {
proxy.startFlowDynamic(clazz, party).returnValue.getOrThrow()
}
}
} finally {
bankA.stop()
bankB.stop()
private fun DriverDSLExposedInterface.installIsolatedCordappTo(nodeName: CordaX500Name) {
// Copy the app jar to the first node. The second won't have it.
val path = (baseDirectory(nodeName.toString()) / "plugins").createDirectories() / "isolated.jar"
logger.info("Installing isolated jar to $path")
isolatedJAR.openStream().buffered().use { input ->
Files.newOutputStream(path).buffered().use { output ->
input.copyTo(output)
}
}
}

private fun DriverDSLExposedInterface.createTwoNodesAndNotary(): List<NodeHandle> {
val adminUser = User("admin", "admin", permissions = setOf("ALL"))
return listOf(
startNode(providedName = bankAName, rpcUsers = listOf(adminUser)),
startNode(providedName = bankBName, rpcUsers = listOf(adminUser)),
startNode(providedName = notaryName, rpcUsers = listOf(adminUser), advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
).transpose().getOrThrow() // Wait for all nodes to start up.
}
}
9 changes: 4 additions & 5 deletions node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.statemachine.appName
import net.corda.node.services.statemachine.flowVersionAndInitiatingClass
import net.corda.node.services.statemachine.*
import net.corda.node.services.transactions.*
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
Expand Down Expand Up @@ -190,7 +187,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
checkpointStorage,
serverThread,
database,
busyNodeLatch)
busyNodeLatch,
cordappLoader.appClassLoader)

smm.tokenizableServices.addAll(tokenizableServices)

Expand All @@ -213,6 +211,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
registerCordappFlows()
_services.rpcFlows += cordappProvider.cordapps.flatMap { it.rpcFlows }
registerCustomSchemas(cordappProvider.cordapps.flatMap { it.customSchemas }.toSet())
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader

runOnStop += network::stop
StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package net.corda.node.services.statemachine

import net.corda.core.internal.VisibleForTesting
import com.google.common.primitives.Primitives
import net.corda.core.cordapp.CordappContext
import net.corda.core.flows.*
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
Expand All @@ -17,7 +18,7 @@ import kotlin.reflect.jvm.javaType
* The internal concrete implementation of the FlowLogicRef marker interface.
*/
@CordaSerializable
data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>) : FlowLogicRef
data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String, val args: Map<String, Any?>) : FlowLogicRef

/**
* A class for conversion to and from [FlowLogic] and [FlowLogicRef] instances.
Expand All @@ -32,6 +33,9 @@ data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String,
* in response to a potential malicious use or buggy update to an app etc.
*/
object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactory {
// TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now
var classloader = javaClass.classLoader

override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) {
throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow")
Expand Down Expand Up @@ -73,17 +77,14 @@ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactor
*/
@VisibleForTesting
internal fun createKotlin(type: Class<out FlowLogic<*>>, args: Map<String, Any?>): FlowLogicRef {
// TODO: we need to capture something about the class loader or "application context" into the ref,
// perhaps as some sort of ThreadLocal style object. For now, just create an empty one.
val appContext = AppContext(emptyList())
// Check we can find a constructor and populate the args to it, but don't call it
createConstructor(type, args)
return FlowLogicRefImpl(type.name, appContext, args)
return FlowLogicRefImpl(type.name, args)
}

fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
if (ref !is FlowLogicRefImpl) throw IllegalFlowLogicException(ref.javaClass, "FlowLogicRef was not created via correct FlowLogicRefFactory interface")
val klass = Class.forName(ref.flowLogicClassName, true, ref.appContext.classLoader).asSubclass(FlowLogic::class.java)
val klass = Class.forName(ref.flowLogicClassName, true, classloader).asSubclass(FlowLogic::class.java)
return createConstructor(klass, ref.args)()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
val checkpointStorage: CheckpointStorage,
val executor: AffinityExecutor,
val database: CordaPersistence,
private val unfinishedFibers: ReusableLatch = ReusableLatch()) {
private val unfinishedFibers: ReusableLatch = ReusableLatch(),
private val classloader: ClassLoader = javaClass.classLoader) {

inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor)

Expand Down Expand Up @@ -380,7 +381,12 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
updateCheckpoint(fiber)
session to initiatedFlowFactory
} catch (e: SessionRejectException) {
logger.warn("${e.logMessage}: $sessionInit")
// TODO: Handle this more gracefully
try {
logger.warn("${e.logMessage}: $sessionInit")
} catch (e: Throwable) {
logger.warn("Problematic session init message during logging", e)
}
sendSessionReject(e.rejectMessage)
return
} catch (e: Exception) {
Expand All @@ -403,7 +409,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,

private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> {
val initiatingFlowClass = try {
Class.forName(sessionInit.initiatingFlowClass).asSubclass(FlowLogic::class.java)
Class.forName(sessionInit.initiatingFlowClass, true, classloader).asSubclass(FlowLogic::class.java)
} catch (e: ClassNotFoundException) {
throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}")
} catch (e: ClassCastException) {
Expand Down

0 comments on commit 84a60d1

Please sign in to comment.