V1 tests and fixes for the ContractConstraints work (#1739)

* V1 tests and fixes for the ContractConstraints work

* More fixes.
This commit is contained in:
Clinton 2017-09-29 17:18:06 +01:00 committed by josecoll
parent e6c2b37f5a
commit 84a60d161b
6 changed files with 73 additions and 60 deletions

View File

@ -25,15 +25,3 @@ 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) // TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes)
@CordaSerializable @CordaSerializable
interface FlowLogicRef 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
}

View File

@ -1,10 +1,12 @@
package net.corda.node.services package net.corda.node.services
import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Contract import net.corda.core.contracts.Contract
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.concurrent.transpose
@ -16,12 +18,18 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor 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.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.TestDependencyInjectionBase 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.driver.driver
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.resetTestSerialization import net.corda.testing.resetTestSerialization
@ -30,6 +38,8 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.Files import java.nio.file.Files
import java.sql.Driver
import kotlin.test.assertFails
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class AttachmentLoadingTests : TestDependencyInjectionBase() { class AttachmentLoadingTests : TestDependencyInjectionBase() {
@ -45,6 +55,13 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() {
val logger = loggerFor<AttachmentLoadingTests>() val logger = loggerFor<AttachmentLoadingTests>()
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!! val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract" 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 private lateinit var services: Services
@ -75,40 +92,42 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() {
@Test @Test
fun `test that attachments retrieved over the network are not used for code`() { fun `test that attachments retrieved over the network are not used for code`() {
driver(initialiseSerialization = false) { driver(initialiseSerialization = false) {
val bankAName = CordaX500Name("BankA", "Zurich", "CH") installIsolatedCordappTo(bankAName)
val bankBName = CordaX500Name("BankB", "Zurich", "CH") val (bankA, bankB, _) = createTwoNodesAndNotary()
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()
}
}
}
@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()
}
}
private fun DriverDSLExposedInterface.installIsolatedCordappTo(nodeName: CordaX500Name) {
// Copy the app jar to the first node. The second won't have it. // Copy the app jar to the first node. The second won't have it.
val path = (baseDirectory(bankAName.toString()) / "plugins").createDirectories() / "isolated.jar" val path = (baseDirectory(nodeName.toString()) / "plugins").createDirectories() / "isolated.jar"
logger.info("Installing isolated jar to $path") logger.info("Installing isolated jar to $path")
isolatedJAR.openStream().buffered().use { input -> isolatedJAR.openStream().buffered().use { input ->
Files.newOutputStream(path).buffered().use { output -> Files.newOutputStream(path).buffered().use { output ->
input.copyTo(output) input.copyTo(output)
} }
} }
}
val admin = User("admin", "admin", permissions = setOf("ALL")) private fun DriverDSLExposedInterface.createTwoNodesAndNotary(): List<NodeHandle> {
val (bankA, bankB) = listOf( val adminUser = User("admin", "admin", permissions = setOf("ALL"))
startNode(providedName = bankAName, rpcUsers = listOf(admin)), return listOf(
startNode(providedName = bankBName, rpcUsers = listOf(admin)) 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. ).transpose().getOrThrow() // Wait for all nodes to start up.
val clazz =
Class.forName("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator", true, URLClassLoader(arrayOf(isolatedJAR)))
.asSubclass(FlowLogic::class.java)
try {
bankA.rpcClientToNode().start("admin", "admin").use { rpc ->
val proxy = rpc.proxy
val party = proxy.wellKnownPartyFromX500Name(bankBName)!!
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()
}
}
} }
} }

View File

@ -57,10 +57,7 @@ import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.*
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.transactions.* import net.corda.node.services.transactions.*
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
@ -190,7 +187,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
checkpointStorage, checkpointStorage,
serverThread, serverThread,
database, database,
busyNodeLatch) busyNodeLatch,
cordappLoader.appClassLoader)
smm.tokenizableServices.addAll(tokenizableServices) smm.tokenizableServices.addAll(tokenizableServices)
@ -213,6 +211,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
registerCordappFlows() registerCordappFlows()
_services.rpcFlows += cordappProvider.cordapps.flatMap { it.rpcFlows } _services.rpcFlows += cordappProvider.cordapps.flatMap { it.rpcFlows }
registerCustomSchemas(cordappProvider.cordapps.flatMap { it.customSchemas }.toSet()) registerCustomSchemas(cordappProvider.cordapps.flatMap { it.customSchemas }.toSet())
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
runOnStop += network::stop runOnStop += network::stop
StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps) StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps)

View File

@ -2,6 +2,7 @@ package net.corda.node.services.statemachine
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import com.google.common.primitives.Primitives import com.google.common.primitives.Primitives
import net.corda.core.cordapp.CordappContext
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
@ -17,7 +18,7 @@ import kotlin.reflect.jvm.javaType
* The internal concrete implementation of the FlowLogicRef marker interface. * The internal concrete implementation of the FlowLogicRef marker interface.
*/ */
@CordaSerializable @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. * A class for conversion to and from [FlowLogic] and [FlowLogicRef] instances.
@ -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. * in response to a potential malicious use or buggy update to an app etc.
*/ */
object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactory { 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 { override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) {
throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow") throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow")
@ -73,17 +77,14 @@ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactor
*/ */
@VisibleForTesting @VisibleForTesting
internal fun createKotlin(type: Class<out FlowLogic<*>>, args: Map<String, Any?>): FlowLogicRef { 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 // Check we can find a constructor and populate the args to it, but don't call it
createConstructor(type, args) createConstructor(type, args)
return FlowLogicRefImpl(type.name, appContext, args) return FlowLogicRefImpl(type.name, args)
} }
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> { fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
if (ref !is FlowLogicRefImpl) throw IllegalFlowLogicException(ref.javaClass, "FlowLogicRef was not created via correct FlowLogicRefFactory interface") 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)() return createConstructor(klass, ref.args)()
} }

View File

@ -79,7 +79,8 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
val checkpointStorage: CheckpointStorage, val checkpointStorage: CheckpointStorage,
val executor: AffinityExecutor, val executor: AffinityExecutor,
val database: CordaPersistence, 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) inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor)
@ -380,7 +381,12 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
updateCheckpoint(fiber) updateCheckpoint(fiber)
session to initiatedFlowFactory session to initiatedFlowFactory
} catch (e: SessionRejectException) { } catch (e: SessionRejectException) {
// TODO: Handle this more gracefully
try {
logger.warn("${e.logMessage}: $sessionInit") logger.warn("${e.logMessage}: $sessionInit")
} catch (e: Throwable) {
logger.warn("Problematic session init message during logging", e)
}
sendSessionReject(e.rejectMessage) sendSessionReject(e.rejectMessage)
return return
} catch (e: Exception) { } catch (e: Exception) {
@ -403,7 +409,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> { private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> {
val initiatingFlowClass = try { val initiatingFlowClass = try {
Class.forName(sessionInit.initiatingFlowClass).asSubclass(FlowLogic::class.java) Class.forName(sessionInit.initiatingFlowClass, true, classloader).asSubclass(FlowLogic::class.java)
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}") throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}")
} catch (e: ClassCastException) { } catch (e: ClassCastException) {