Merge remote-tracking branch 'open/master' into os-merge-6d4bdb8

# Conflicts:
#	docs/source/changelog.rst
#	node-api/build.gradle
#	node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt
#	node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt
#	node/src/integration-test/kotlin/net/corda/node/modes/draining/FlowsDrainingModeContentionTest.kt
#	node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt
This commit is contained in:
Shams Asari
2018-10-15 12:34:29 +01:00
62 changed files with 858 additions and 946 deletions

View File

@ -5825,8 +5825,6 @@ public interface net.corda.testing.driver.DriverDSL
@NotNull @NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.NodeHandle> startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List<net.corda.testing.node.User>, net.corda.testing.driver.VerifierType, java.util.Map<String, ?>, Boolean, String) public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.NodeHandle> startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List<net.corda.testing.node.User>, net.corda.testing.driver.VerifierType, java.util.Map<String, ?>, Boolean, String)
@NotNull @NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.NodeHandle> startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List<net.corda.testing.node.User>, net.corda.testing.driver.VerifierType, java.util.Map<String, ?>, Boolean, String, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>, boolean)
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> startWebserver(net.corda.testing.driver.NodeHandle) public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> startWebserver(net.corda.testing.driver.NodeHandle)
@NotNull @NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> startWebserver(net.corda.testing.driver.NodeHandle, String) public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> startWebserver(net.corda.testing.driver.NodeHandle, String)
@ -5835,10 +5833,7 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O
public <init>() public <init>()
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters) public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map<String, ?>, boolean, boolean) public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map<String, ?>, boolean, boolean)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map<String, ?>, boolean, boolean, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean) public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public final boolean component1() public final boolean component1()
@NotNull @NotNull
public final java.util.List<String> component10() public final java.util.List<String> component10()
@ -6031,75 +6026,6 @@ public static final class net.corda.testing.driver.PortAllocation$Incremental ex
public final java.util.concurrent.atomic.AtomicInteger getPortCounter() public final java.util.concurrent.atomic.AtomicInteger getPortCounter()
public int nextPort() public int nextPort()
## ##
@DoNotImplement
public interface net.corda.testing.driver.TestCorDapp
@NotNull
public abstract java.util.Set<Class<?>> getClasses()
@NotNull
public abstract String getName()
@NotNull
public abstract java.util.Set<java.net.URL> getResources()
@NotNull
public abstract String getTitle()
@NotNull
public abstract String getVendor()
@NotNull
public abstract String getVersion()
@NotNull
public abstract java.nio.file.Path packageAsJarInDirectory(java.nio.file.Path)
public abstract void packageAsJarWithPath(java.nio.file.Path)
##
public static final class net.corda.testing.driver.TestCorDapp$Factory extends java.lang.Object
public <init>()
@NotNull
public static final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set<? extends Class<?>>, kotlin.jvm.functions.Function2<? super String, ? super java.net.URL, Boolean>)
public static final net.corda.testing.driver.TestCorDapp$Factory$Companion Companion
##
public static final class net.corda.testing.driver.TestCorDapp$Factory$Companion extends java.lang.Object
@NotNull
public final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set<? extends Class<?>>, kotlin.jvm.functions.Function2<? super String, ? super java.net.URL, Boolean>)
##
@DoNotImplement
public static interface net.corda.testing.driver.TestCorDapp$Mutable extends net.corda.testing.driver.TestCorDapp
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minus(Class<?>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(Package)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(Package, Package...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(String, String...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(java.util.Set<String>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusResource(String, java.net.URL)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plus(Class<?>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(Package)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(Package, Package...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(String, String...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(java.util.Set<String>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusResource(String, java.net.URL)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withClasses(java.util.Set<? extends Class<?>>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withName(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withTitle(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withVendor(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withVersion(String)
##
public final class net.corda.testing.driver.VerifierType extends java.lang.Enum public final class net.corda.testing.driver.VerifierType extends java.lang.Enum
protected <init>() protected <init>()
public static net.corda.testing.driver.VerifierType valueOf(String) public static net.corda.testing.driver.VerifierType valueOf(String)
@ -6241,9 +6167,9 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object
@NotNull @NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>) public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
@NotNull @NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>) public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
@NotNull @NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>) public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull @NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters) public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters)
@NotNull @NotNull
@ -6259,9 +6185,9 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object
@NotNull @NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>) public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
@NotNull @NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>) public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
@NotNull @NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>) public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull @NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters) public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters)
@NotNull @NotNull
@ -6342,8 +6268,7 @@ public final class net.corda.testing.node.MockNetworkParameters extends java.lan
public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object
public <init>() public <init>()
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>) public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>) public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
@Nullable @Nullable
public final Integer component1() public final Integer component1()
@Nullable @Nullable
@ -6355,9 +6280,7 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O
@NotNull @NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>) public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
@NotNull @NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>) public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
@NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public boolean equals(Object) public boolean equals(Object)
@NotNull @NotNull
public final kotlin.jvm.functions.Function1<net.corda.node.services.config.NodeConfiguration, Object> getConfigOverrides() public final kotlin.jvm.functions.Function1<net.corda.node.services.config.NodeConfiguration, Object> getConfigOverrides()

View File

@ -75,7 +75,7 @@ buildscript {
ext.commons_cli_version = '1.4' ext.commons_cli_version = '1.4'
ext.protonj_version = '0.27.1' // This is now aligned with the Artemis version, but retaining in case we ever need to diverge again for a bug fix. ext.protonj_version = '0.27.1' // This is now aligned with the Artemis version, but retaining in case we ever need to diverge again for a bug fix.
ext.snappy_version = '0.4' ext.snappy_version = '0.4'
ext.fast_classpath_scanner_version = '2.12.3' ext.class_graph_version = '4.2.12'
ext.jcabi_manifests_version = '1.1' ext.jcabi_manifests_version = '1.1'
ext.picocli_version = '3.5.2' ext.picocli_version = '3.5.2'

View File

@ -11,8 +11,11 @@ import java.util.jar.JarInputStream
*/ */
object JarSignatureCollector { object JarSignatureCollector {
/** @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File> */ /**
private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex() * @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File>
* also accepting *.EC as this can be created and accepted by jarsigner tool @see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html
* and Java Security Manager. */
private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA|EC)|SIG-.*)".toRegex()
/** /**
* Returns an ordered list of every [Party] which has signed every signable item in the given [JarInputStream]. * Returns an ordered list of every [Party] which has signed every signable item in the given [JarInputStream].

View File

@ -2,8 +2,6 @@ package net.corda.core.flows
import com.natpryce.hamkrest.and import com.natpryce.hamkrest.and
import com.natpryce.hamkrest.assertion.assert import com.natpryce.hamkrest.assertion.assert
import net.corda.testing.internal.matchers.flow.willReturn
import net.corda.testing.internal.matchers.flow.willThrow
import net.corda.core.flows.mixins.WithFinality import net.corda.core.flows.mixins.WithFinality
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -12,6 +10,8 @@ import net.corda.finance.POUNDS
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.issuedBy import net.corda.finance.issuedBy
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.matchers.flow.willReturn
import net.corda.testing.internal.matchers.flow.willThrow
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.TestStartedNode import net.corda.testing.node.internal.TestStartedNode
import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.cordappsForPackages
@ -21,7 +21,10 @@ import org.junit.Test
class FinalityFlowTests : WithFinality { class FinalityFlowTests : WithFinality {
companion object { companion object {
private val CHARLIE = TestIdentity(CHARLIE_NAME, 90).party private val CHARLIE = TestIdentity(CHARLIE_NAME, 90).party
private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset","net.corda.finance.schemas")) private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(
"net.corda.finance.contracts.asset",
"net.corda.finance.schemas"
))
@JvmStatic @JvmStatic
@AfterClass @AfterClass
@ -33,7 +36,6 @@ class FinalityFlowTests : WithFinality {
private val aliceNode = makeNode(ALICE_NAME) private val aliceNode = makeNode(ALICE_NAME)
private val bobNode = makeNode(BOB_NAME) private val bobNode = makeNode(BOB_NAME)
private val alice = aliceNode.info.singleIdentity()
private val bob = bobNode.info.singleIdentity() private val bob = bobNode.info.singleIdentity()
private val notary = mockNet.defaultNotaryIdentity private val notary = mockNet.defaultNotaryIdentity
@ -59,11 +61,9 @@ class FinalityFlowTests : WithFinality {
} }
private fun TestStartedNode.signCashTransactionWith(other: Party): SignedTransaction { private fun TestStartedNode.signCashTransactionWith(other: Party): SignedTransaction {
val amount = 1000.POUNDS.issuedBy(alice.ref(0)) val amount = 1000.POUNDS.issuedBy(info.singleIdentity().ref(0))
val builder = TransactionBuilder(notary) val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, other, notary) Cash().generateIssue(builder, amount, other, notary)
return services.signInitialTransaction(builder) return services.signInitialTransaction(builder)
} }
} }

View File

@ -55,13 +55,13 @@ internal class CreateRefState : FlowLogic<SignedTransaction>() {
} }
// A flow to update a specific reference state. // A flow to update a specific reference state.
internal class UpdateRefState(private val stateAndRef: StateAndRef<ContractState>) : FlowLogic<SignedTransaction>() { internal class UpdateRefState(private val stateAndRef: StateAndRef<RefState.State>) : FlowLogic<SignedTransaction>() {
@Suspendable @Suspendable
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
val notary = serviceHub.networkMapCache.notaryIdentities.first() val notary = serviceHub.networkMapCache.notaryIdentities.first()
val stx = serviceHub.signInitialTransaction(TransactionBuilder(notary = notary).apply { val stx = serviceHub.signInitialTransaction(TransactionBuilder(notary = notary).apply {
addInputState(stateAndRef) addInputState(stateAndRef)
addOutputState((stateAndRef.state.data as RefState.State).update(), RefState.CONTRACT_ID) addOutputState(stateAndRef.state.data.update(), RefState.CONTRACT_ID)
addCommand(RefState.Update(), listOf(ourIdentity.owningKey)) addCommand(RefState.Update(), listOf(ourIdentity.owningKey))
}) })
return subFlow(FinalityFlow(stx)) return subFlow(FinalityFlow(stx))
@ -160,5 +160,4 @@ class WithReferencedStatesFlowTests {
val result = useRefTx.getOrThrow() val result = useRefTx.getOrThrow()
assertEquals(updatedRefState.ref, result.tx.references.single()) assertEquals(updatedRefState.ref, result.tx.references.single())
} }
} }

View File

@ -4,6 +4,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.AfterClass import org.junit.AfterClass
@ -38,15 +39,18 @@ class JarSignatureCollectorTest {
private const val ALICE_PASS = "alicepass" private const val ALICE_PASS = "alicepass"
private const val BOB = "bob" private const val BOB = "bob"
private const val BOB_PASS = "bobpass" private const val BOB_PASS = "bobpass"
private const val CHARLIE = "Charlie"
private const val CHARLIE_PASS = "charliepass"
private fun generateKey(alias: String, password: String, name: CordaX500Name) = private fun generateKey(alias: String, password: String, name: CordaX500Name, keyalg: String = "RSA") =
execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", alias, "-keypass", password, "-dname", name.toString()) execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", keyalg, "-alias", alias, "-keypass", password, "-dname", name.toString())
@BeforeClass @BeforeClass
@JvmStatic @JvmStatic
fun beforeClass() { fun beforeClass() {
generateKey(ALICE, ALICE_PASS, ALICE_NAME) generateKey(ALICE, ALICE_PASS, ALICE_NAME)
generateKey(BOB, BOB_PASS, BOB_NAME) generateKey(BOB, BOB_PASS, BOB_NAME)
generateKey(CHARLIE, CHARLIE_PASS, CHARLIE_NAME, "EC")
(dir / "_signable1").writeLines(listOf("signable1")) (dir / "_signable1").writeLines(listOf("signable1"))
(dir / "_signable2").writeLines(listOf("signable2")) (dir / "_signable2").writeLines(listOf("signable2"))
@ -141,6 +145,18 @@ class JarSignatureCollectorTest {
assertFailsWith<SecurityException> { getJarSigners() } assertFailsWith<SecurityException> { getJarSigners() }
} }
// Signing using EC algorithm produced JAR File spec incompatible signature block (META-INF/*.EC) which is anyway accepted by jarsiner, see [JarSignatureCollector]
@Test
fun `one signer with EC sign algorithm`() {
createJar("_signable1", "_signable2")
signJar(CHARLIE, CHARLIE_PASS)
assertEquals(listOf(CHARLIE_NAME), getJarSigners().names) // We only reused CHARLIE's distinguished name, so the keys will be different.
(dir / "my-dir").createDirectory()
updateJar("my-dir")
assertEquals(listOf(CHARLIE_NAME), getJarSigners().names) // Unsigned directory is irrelevant.
}
//region Helper functions //region Helper functions
private fun createJar(vararg contents: String) = private fun createJar(vararg contents: String) =
execute(*(arrayOf("jar", "cvf", FILENAME) + contents)) execute(*(arrayOf("jar", "cvf", FILENAME) + contents))

View File

@ -36,8 +36,8 @@ dependencies {
compile "org.ow2.asm:asm-tree:$asm_version" compile "org.ow2.asm:asm-tree:$asm_version"
compile "org.ow2.asm:asm-commons:$asm_version" compile "org.ow2.asm:asm-commons:$asm_version"
// Classpath scanner // ClassGraph: classpath scanning
shadow "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" shadow "io.github.classgraph:classgraph:$class_graph_version"
// Test utilities // Test utilities
testCompile "junit:junit:$junit_version" testCompile "junit:junit:$junit_version"

View File

@ -1,8 +1,9 @@
@file:JvmName("Utilities") @file:JvmName("Utilities")
package net.corda.djvm.tools.cli package net.corda.djvm.tools.cli
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.classgraph.ClassGraph
import java.lang.reflect.Modifier import java.lang.reflect.Modifier.isAbstract
import java.lang.reflect.Modifier.isStatic
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
@ -92,13 +93,10 @@ val userClassPath: String = System.getProperty("java.class.path")
* Get a reference of each concrete class that implements interface or class [T]. * Get a reference of each concrete class that implements interface or class [T].
*/ */
inline fun <reified T> find(scanSpec: String = "net/corda/djvm"): List<Class<*>> { inline fun <reified T> find(scanSpec: String = "net/corda/djvm"): List<Class<*>> {
val references = mutableListOf<Class<*>>() return ClassGraph()
FastClasspathScanner(scanSpec) .whitelistPaths(scanSpec)
.matchClassesImplementing(T::class.java) { clazz -> .enableAllInfo()
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
references.add(clazz)
}
}
.scan() .scan()
return references .use { it.getClassesImplementing(T::class.java.name).loadClasses(T::class.java) }
.filter { !isAbstract(it.modifiers) && !isStatic(it.modifiers) }
} }

View File

@ -4,8 +4,8 @@ import sandbox.java.util.function.Supplier;
/** /**
* Everything inside the sandbox is single-threaded, so this * Everything inside the sandbox is single-threaded, so this
* implementation of ThreadLocal<T> is sufficient. * implementation of ThreadLocal is sufficient.
* @param <T> * @param <T> Underlying type of this thread-local variable.
*/ */
@SuppressWarnings({"unused", "WeakerAccess"}) @SuppressWarnings({"unused", "WeakerAccess"})
public class ThreadLocal<T> extends Object { public class ThreadLocal<T> extends Object {

View File

@ -1,6 +1,6 @@
package net.corda.djvm.utilities package net.corda.djvm.utilities
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.classgraph.ClassGraph
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
/** /**
@ -13,19 +13,19 @@ object Discovery {
* Get an instance of each concrete class that implements interface or class [T]. * Get an instance of each concrete class that implements interface or class [T].
*/ */
inline fun <reified T> find(): List<T> { inline fun <reified T> find(): List<T> {
val instances = mutableListOf<T>() return ClassGraph()
FastClasspathScanner("net/corda/djvm") .whitelistPaths("net/corda/djvm")
.matchClassesImplementing(T::class.java) { clazz -> .enableAllInfo()
if (clazz.modifiers and FORBIDDEN_CLASS_MASK == 0) {
try {
instances.add(clazz.newInstance())
} catch (exception: Throwable) {
throw Exception("Unable to instantiate ${clazz.name}", exception)
}
}
}
.scan() .scan()
return instances .use { it.getClassesImplementing(T::class.java.name).loadClasses(T::class.java) }
.filter { it.modifiers and FORBIDDEN_CLASS_MASK == 0 }
.map {
try {
it.newInstance()
} catch (exception: Throwable) {
throw Exception("Unable to instantiate ${it.name}", exception)
}
} }
} }
}

View File

@ -5,16 +5,18 @@ CorDapps
:maxdepth: 1 :maxdepth: 1
cordapp-overview cordapp-overview
getting-set-up
tutorial-cordapp
building-a-cordapp-samples
writing-a-cordapp writing-a-cordapp
cordapp-build-systems
building-against-master
debugging-a-cordapp debugging-a-cordapp
upgrade-notes upgrade-notes
upgrading-cordapps upgrading-cordapps
cordapp-build-systems
building-against-master
corda-api
secure-coding-guidelines secure-coding-guidelines
corda-api
flow-cookbook flow-cookbook
cheat-sheet
vault vault
soft-locking soft-locking
cheat-sheet
building-a-cordapp-samples

View File

@ -15,7 +15,8 @@ Unreleased
* New overload for ``CordaRPCClient.start()`` method allowing to specify target legal identity to use for RPC call. * New overload for ``CordaRPCClient.start()`` method allowing to specify target legal identity to use for RPC call.
* Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default queries will be case sensitive. * Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default
queries will be case sensitive.
* Getter added to ``CordaRPCOps`` for the node's network parameters. * Getter added to ``CordaRPCOps`` for the node's network parameters.
@ -32,7 +33,8 @@ Unreleased
* "app", "rpc", "p2p" and "unknown" are no longer allowed as uploader values when importing attachments. These are used * "app", "rpc", "p2p" and "unknown" are no longer allowed as uploader values when importing attachments. These are used
internally in security sensitive code. internally in security sensitive code.
* Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork`` and ``MockServices``. * Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork``
and ``MockServices``.
* Change type of the ``checkpoint_value`` column. Please check the upgrade-notes on how to update your database. * Change type of the ``checkpoint_value`` column. Please check the upgrade-notes on how to update your database.
@ -51,7 +53,8 @@ Unreleased
rather than IllegalStateException. rather than IllegalStateException.
* The Corda JPA entities no longer implement java.io.Serializable, as this was causing persistence errors in obscure cases. * The Corda JPA entities no longer implement java.io.Serializable, as this was causing persistence errors in obscure cases.
Java serialization is disabled globally in the node, but in the unlikely event you were relying on these types being Java serializable please contact us. Java serialization is disabled globally in the node, but in the unlikely event you were relying on these types being Java
serializable please contact us.
* Remove all references to the out-of-process transaction verification. * Remove all references to the out-of-process transaction verification.
@ -111,7 +114,8 @@ Unreleased
* The node's configuration is only printed on startup if ``devMode`` is ``true``, avoiding the risk of printing passwords * The node's configuration is only printed on startup if ``devMode`` is ``true``, avoiding the risk of printing passwords
in a production setup. in a production setup.
* ``NodeStartup`` will now only print node's configuration if ``devMode`` is ``true``, avoiding the risk of printing passwords in a production setup. * ``NodeStartup`` will now only print node's configuration if ``devMode`` is ``true``, avoiding the risk of printing passwords
in a production setup.
* SLF4J's MDC will now only be printed to the console if not empty. No more log lines ending with "{}". * SLF4J's MDC will now only be printed to the console if not empty. No more log lines ending with "{}".
@ -176,13 +180,15 @@ Unreleased
* Added public support for creating ``CordaRPCClient`` using SSL. For this to work the node needs to provide client applications * Added public support for creating ``CordaRPCClient`` using SSL. For this to work the node needs to provide client applications
a certificate to be added to a truststore. See :doc:`tutorial-clientrpc-api` a certificate to be added to a truststore. See :doc:`tutorial-clientrpc-api`
* The node RPC broker opens 2 endpoints that are configured with ``address`` and ``adminAddress``. RPC Clients would connect to the address, while the node will connect *The node RPC broker opens 2 endpoints that are configured with ``address`` and ``adminAddress``. RPC Clients would connect
to the adminAddress. Previously if ssl was enabled for RPC the ``adminAddress`` was equal to ``address``. to the address, while the node will connect to the adminAddress. Previously if ssl was enabled for RPC the ``adminAddress``
was equal to ``address``.
* Upgraded H2 to v1.4.197 * Upgraded H2 to v1.4.197
* Shell (embedded available only in dev mode or via SSH) connects to the node via RPC instead of using the ``CordaRPCOps`` object directly. * Shell (embedded available only in dev mode or via SSH) connects to the node via RPC instead of using the ``CordaRPCOps``
To enable RPC connectivity ensure nodes ``rpcSettings.address`` and ``rpcSettings.adminAddress`` settings are present. object directly. To enable RPC connectivity ensure nodes ``rpcSettings.address`` and ``rpcSettings.adminAddress`` settings
are present.
* Changes to the network bootstrapper: * Changes to the network bootstrapper:
@ -190,7 +196,8 @@ Unreleased
whitelist. whitelist.
* The CorDapp jars are also copied to each nodes' ``cordapps`` directory. * The CorDapp jars are also copied to each nodes' ``cordapps`` directory.
* Errors thrown by a Corda node will now reported to a calling RPC client with attention to serialization and obfuscation of internal data. * Errors thrown by a Corda node will now reported to a calling RPC client with attention to serialization and obfuscation
of internal data.
* Serializing an inner class (non-static nested class in Java, inner class in Kotlin) will be rejected explicitly by the serialization * Serializing an inner class (non-static nested class in Java, inner class in Kotlin) will be rejected explicitly by the serialization
framework. Prior to this change it didn't work, but the error thrown was opaque (complaining about too few arguments framework. Prior to this change it didn't work, but the error thrown was opaque (complaining about too few arguments
@ -198,13 +205,15 @@ Unreleased
reference to the outer class) as per the Java documentation `here <https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html>`_ reference to the outer class) as per the Java documentation `here <https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html>`_
we are disallowing this as the paradigm in general makes little sense for contract states. we are disallowing this as the paradigm in general makes little sense for contract states.
* Node can be shut down abruptly by ``shutdown`` function in ``CordaRPCOps`` or gracefully (draining flows first) through ``gracefulShutdown`` command from shell. * Node can be shut down abruptly by ``shutdown`` function in ``CordaRPCOps`` or gracefully (draining flows first) through
``gracefulShutdown`` command from shell.
* API change: ``net.corda.core.schemas.PersistentStateRef`` fields (index and txId) are now non-nullable. * API change: ``net.corda.core.schemas.PersistentStateRef`` fields (index and txId) are now non-nullable.
The fields were always effectively non-nullable - values were set from non-nullable fields of other objects. The fields were always effectively non-nullable - values were set from non-nullable fields of other objects.
The class is used as database Primary Key columns of other entities and databases already impose those columns as non-nullable The class is used as database Primary Key columns of other entities and databases already impose those columns as non-nullable
(even if JPA annotation nullable=false was absent). (even if JPA annotation nullable=false was absent).
In case your Cordapps use this entity class to persist data in own custom tables as non Primary Key columns refer to :doc:`upgrade-notes` for upgrade instructions. In case your Cordapps use this entity class to persist data in own custom tables as non Primary Key columns refer to
:doc:`upgrade-notes` for upgrade instructions.
* Adding a public method to check if a public key satisfies Corda recommended algorithm specs, `Crypto.validatePublicKey(java.security.PublicKey)`. * Adding a public method to check if a public key satisfies Corda recommended algorithm specs, `Crypto.validatePublicKey(java.security.PublicKey)`.
For instance, this method will check if an ECC key lies on a valid curve or if an RSA key is >= 2048bits. This might For instance, this method will check if an ECC key lies on a valid curve or if an RSA key is >= 2048bits. This might
@ -274,9 +283,10 @@ Corda Enterprise 3.0 Developer Preview
* Per CorDapp configuration is now exposed. ``CordappContext`` now exposes a ``CordappConfig`` object that is populated * Per CorDapp configuration is now exposed. ``CordappContext`` now exposes a ``CordappConfig`` object that is populated
at CorDapp context creation time from a file source during runtime. at CorDapp context creation time from a file source during runtime.
* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts. * Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to
In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed. support graceful node shutdown/restarts. In particular, when this mode is on, new flows through RPC will be rejected,
This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown. scheduled flows will be ignored, and initial session messages will not be consumed. This will ensure that the number of
checkpoints will strictly diminish with time, allowing for a clean shutdown.
* Make the serialisation finger-printer a pluggable entity rather than hard wiring into the factory * Make the serialisation finger-printer a pluggable entity rather than hard wiring into the factory
@ -288,17 +298,19 @@ Corda Enterprise 3.0 Developer Preview
* Modified ``CordaRPCClient`` constructor to take a ``SSLConfiguration?`` additional parameter, defaulted to ``null``. * Modified ``CordaRPCClient`` constructor to take a ``SSLConfiguration?`` additional parameter, defaulted to ``null``.
* Introduced ``CertificateChainCheckPolicy.UsernameMustMatchCommonName`` sub-type, allowing customers to optionally enforce username == CN condition on RPC SSL certificates. * Introduced ``CertificateChainCheckPolicy.UsernameMustMatchCommonName`` sub-type, allowing customers to optionally enforce
username == CN condition on RPC SSL certificates.
* Modified ``DriverDSL`` and sub-types to allow specifying RPC settings for the Node. * Modified ``DriverDSL`` and sub-types to allow specifying RPC settings for the Node.
* Modified the ``DriverDSL`` to start Cordformation nodes allowing automatic generation of "rpcSettings.adminAddress" in case "rcpSettings.useSsl" is ``false`` (the default). * Modified the ``DriverDSL`` to start Cordformation nodes allowing automatic generation of "rpcSettings.adminAddress" in case
"rcpSettings.useSsl" is ``false`` (the default).
* Introduced ``UnsafeCertificatesFactory`` allowing programmatic generation of X509 certificates for test purposes. * Introduced ``UnsafeCertificatesFactory`` allowing programmatic generation of X509 certificates for test purposes.
* JPA Mapping annotations for States extending ``CommonSchemaV1.LinearState`` and ``CommonSchemaV1.FungibleState`` on the * JPA Mapping annotations for States extending ``CommonSchemaV1.LinearState`` and ``CommonSchemaV1.FungibleState`` on the
`participants` collection need to be moved to the actual class. This allows to properly specify the unique table name per a collection. `participants` collection need to be moved to the actual class. This allows to properly specify the unique table name per
See: DummyDealStateSchemaV1.PersistentDummyDealState a collection. See: DummyDealStateSchemaV1.PersistentDummyDealState
* JPA Mapping annotations for States extending ``CommonSchemaV1.LinearState`` and ``CommonSchemaV1.FungibleState`` on the * JPA Mapping annotations for States extending ``CommonSchemaV1.LinearState`` and ``CommonSchemaV1.FungibleState`` on the
`participants` collection need to be moved to the actual State class. This allows developers to properly specify `participants` collection need to be moved to the actual State class. This allows developers to properly specify
@ -625,7 +637,9 @@ Corda 1.0
* Vault query soft locking enhancements and deprecations * Vault query soft locking enhancements and deprecations
* removed original ``VaultService`` ``softLockedStates`` query mechanism. * removed original ``VaultService`` ``softLockedStates`` query mechanism.
* introduced improved ``SoftLockingCondition`` filterable attribute in ``VaultQueryCriteria`` to enable specification of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified by set of lock ids) * introduced improved ``SoftLockingCondition`` filterable attribute in ``VaultQueryCriteria`` to enable specification of
different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified by set
of lock ids)
* Trader demo now issues cash and commercial paper directly from the bank node, rather than the seller node self-issuing * Trader demo now issues cash and commercial paper directly from the bank node, rather than the seller node self-issuing
commercial paper but labelling it as if issued by the bank. commercial paper but labelling it as if issued by the bank.
@ -655,7 +669,8 @@ Corda 1.0
This may require adjusting imports of Cash flow references and also of ``StartFlow`` permission in ``gradle.build`` files. This may require adjusting imports of Cash flow references and also of ``StartFlow`` permission in ``gradle.build`` files.
* Removed the concept of relevancy from ``LinearState``. The ``ContractState``'s relevancy to the vault can be determined * Removed the concept of relevancy from ``LinearState``. The ``ContractState``'s relevancy to the vault can be determined
by the flow context, the vault will process any transaction from a flow which is not derived from transaction resolution verification. by the flow context, the vault will process any transaction from a flow which is not derived from transaction resolution
verification.
* Removed the tolerance attribute from ``TimeWindowChecker`` and thus, there is no extra tolerance on the notary side anymore. * Removed the tolerance attribute from ``TimeWindowChecker`` and thus, there is no extra tolerance on the notary side anymore.

View File

@ -0,0 +1,110 @@
# Package namespace ownership
This design document outlines a new Corda feature that allows a compatibility zone to give ownership of parts of the Java package namespace to certain users.
"*There are only two hard problems in computer science: 1. Cache invalidation, 2. Naming things, 3. Off by one errors*"
## Background
Corda implements a decentralised database that can be unilaterally extended with new data types and logic by its users, without any involvement by the closest equivalent we have to administrators (the "zone operator"). Even informing them is not required.
This design minimises the power zone operators have and ensures deploying new apps can be fast and cheap - it's limited only by the speed with which the users themselves can move. But it introduces problematic levels of namespace complexity which can make programming securely harder than in regular non-decentralised programming.
#### Java namespaces
A typical Java application, seen from the JVM level, has a flat namespace in which a single string name binds to a single class. In object oriented programming a class defines both a data structure and the code used to enforce various invariants like "a person's age may not be negative", so this allows a developer to reason about what the identifier `com.example.Person` really means throughout the lifetime of his program.
More complex Java applications may have a nested namespace using classloaders, thus inside a JVM a class is actually a pair of (classloader pointer, class name) and this can be used to support tricks like having two different versions of the same class in use simultaneously. The downside is more complexity for the developer to deal with. When things get mixed up this can surface (in Java 8) as nonsensical error messages like "com.example.Person cannot be casted to com.example.Person". In Java 9 classloaders were finally given names so these errors make more sense.
#### Corda namespaces
Corda faces an extension of the Java namespace problem - we have a global namespace in which malicious adversaries might be choosing names to be deliberately confusing. Nothing forces an app developer to follow the standard conventions for Java package or class names - someone could make an app that uses the same class name as one of your own apps. Corda needs to keep these two different classes, from different origins, separated.
On the core ledger this is done by associating each state with an _attachment_. The attachment is the JAR file that contains the class files used by states. To load a state, a classloader is defined that uses the attachments on a transaction, and then the state class is loaded via that classloader.
With this infrastructure in place, the Corda node and JVM can internally keep two classes that share the same name separated. The name of the state is, in effect, a list of attachments (hashes of JAR files) combined with a regular class name.
#### Namespaces and versioning
Names and namespaces are a critical part of how platforms of any kind handle software evolution. If component A is verifying the precise content of component B, e.g. by hashing it, then there can be no agility - component B can never be upgraded. Sometimes this is what's wanted. But usually you want the indirection of a name or set of names that stands in for some behaviour. Exactly how that behaviour is provided is abstracted away behind the mapping of the namespace to concrete artifacts.
Versioning and resistance to malicious attack are likewise heavily interrelated, because given two different codebases that export the same names, it's possible that one is a legitimate upgrade which changes the logic behind the names in beneficial ways, and the other is an imposter that changes the logic in malicious ways. It's important to keep the differences straight, which can be hard because by their very nature, two versions of the same app tend to be nearly identical.
#### Namespace complexity
Reasoning about namespaces is hard and has historically led to security flaws in many platforms.
Although the Corda namespace system _can_ keep overlapping but distinct apps separated, that unfortunately doesn't mean that everywhere it actually does. In a few places Corda does not currently provide all the data needed to work with full state names, although we are adding this data to RPC in Corda 4.
Even if Corda was sure to get every detail of this right in every area, a full ecosystem consists of many programs written by app developers - not just contracts and flows, but also RPC clients, bridges from internal systems and so on. It is unreasonable to expect developers to fully keep track of Corda compound names everywhere throughout the entire pipeline of tools and processes that may surround the node: some of them will lose track of the attachments list and end up with only a class name, and others will do things like serialise to JSON in which even type names go missing.
Although we can work on improving our support and APIs for working with sophisticated compound names, we should also allow people to work with simpler namespaces again - like just Java class names. This involves a small sacrifice of decentralisation but the increase in security is probably worth it for most developers.
## Goals
* Provide a way to reduce the complexity of naming and working with names in Corda by allowing for a small amount of centralisation, balanced by a reduction in developer mental load.
* Keep it optional for both zones and developers.
* Allow most developers to work just with ordinary Java class names, without needing to consider the complexities of a decentralised namespace.
## Non-goals
* Directly make it easier to work with "decentralised names". This can be a project that comes later.
## Design
To make it harder to accidentally write insecure code, we would like to support a compromise configuration in which a compatibility zone can publish a map of Java package namespaces to public keys. An app/attachment JAR may only define a class in that namespace if it is signed by the given public key. Using this feature would make a zone slightly less decentralised, in order to obtain a significant reduction in mental overhead for developers.
Example of how the network parameters would be extended, in pseudo-code:
```kotlin
data class JavaPackageName(name: String) {
init { /* verify 'name' is a valid Java package name */ }
}
data class NetworkParameters(
...
val packageOwnership: Map<JavaPackageName, PublicKey>
)
```
Where the `PublicKey` object can be any of the algorithms supported by signature constraints. The map defines a set of dotted package names like `com.foo.bar` where any class in that package or any sub-package of that package is considered to match (so `com.foo.bar.baz.boz.Bish` is a match but `com.foo.barrier` does not).
When a class is loaded from an attachment or application JAR signature checking is enabled. If the package of the class matches one of the owned namespaces, the JAR must be have enough signatures to satisfy the PublicKey (there may need to be more than one if the PublicKey is composite).
Please note the following:
* It's OK to have unsigned JARs.
* It's OK to have JARs that are signed, but for which there are no claims in the network parameters.
* It's OK if entries in the map are removed (system becomes more open). If entries in the map are added, this could cause consensus failures if people are still using old unsigned versions of the app.
* The map specifies keys not certificate chains, therefore, the keys do not have to chain off the identity key of a zone member. App developers do not need to be members of a zone for their app to be used there.
From a privacy and decentralisation perspective, the zone operator *may* learn who is developing apps in their zone or (in cases where a vendor makes a single app and thus it's obvious) which apps are being used. This is not ideal, but there are mitigations:
* The privacy leak is optional.
* The zone operator still doesn't learn who is using which apps.
* There is no obligation for Java package namespaces to correlate obviously to real world identities or products. For example you could register a trivial "front" domain and claim ownership of that, then use it for your apps. The zone operator would see only a codename.
#### Claiming a namespace
The exact mechanism used to claim a namespace is up to the zone operator. A typical approach would be to accept an SSL certificate with the domain in it as proof of domain ownership, or to accept an email from that domain as long as the domain is using DKIM to prevent from header spoofing.
#### The vault API
The vault query API is an example of how tricky it can be to manage truly decentralised namespaces. The `Vault.Page` class does not include constraint information for a state. Therefore, if a generic app were to be storing states of many different types to the vault without having the specific apps installed, it might be possible for someone to create a confusing name e.g. an app created by MiniCorp could export a class named `com.megacorp.example.Token` and this would be mapped by the RPC deserialisation logic to the actual MegaCorp app - the RPC client would have no way to know this had happened, even if the user was correctly checking, which it's unlikely they would.
The `StateMetadata` class can be easily extended to include constraint information, to make safely programming against a decentralised namespace possible. As part of this work this extension will be made.
But the new field would still need to be used - a subtle detail that would be easy to overlook. Package namespace ownership ensures that if you have an app installed locally on the client side that implements `com.megacorp.example` , then that code is likely to match closely enough with the version that was verified by the node.

View File

@ -28,12 +28,12 @@ import java.security.GeneralSecurityException;
import java.security.PublicKey; import java.security.PublicKey;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Collections.*;
import static net.corda.core.contracts.ContractsDSL.requireThat; import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.core.crypto.Crypto.generateKeyPair; import static net.corda.core.crypto.Crypto.generateKeyPair;
@ -529,7 +529,7 @@ public class FlowCookbook {
// other required signers using ``CollectSignaturesFlow``. // other required signers using ``CollectSignaturesFlow``.
// The responder flow will need to call ``SignTransactionFlow``. // The responder flow will need to call ``SignTransactionFlow``.
// DOCSTART 15 // DOCSTART 15
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, Collections.emptySet(), SIGS_GATHERING.childProgressTracker())); SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, emptySet(), SIGS_GATHERING.childProgressTracker()));
// DOCEND 15 // DOCEND 15
/*------------------------ /*------------------------
@ -558,7 +558,7 @@ public class FlowCookbook {
// ``Arrays.asList(counterpartyPubKey)`` instead of // ``Arrays.asList(counterpartyPubKey)`` instead of
// ``Collections.singletonList(counterpartyPubKey)``. // ``Collections.singletonList(counterpartyPubKey)``.
// DOCSTART 54 // DOCSTART 54
onceSignedTx.verifySignaturesExcept(Collections.singletonList(counterpartyPubKey)); onceSignedTx.verifySignaturesExcept(singletonList(counterpartyPubKey));
// DOCEND 54 // DOCEND 54
// We can also choose to only check the signatures that are // We can also choose to only check the signatures that are
@ -584,7 +584,7 @@ public class FlowCookbook {
// We can also choose to send it to additional parties who aren't one // We can also choose to send it to additional parties who aren't one
// of the state's participants. // of the state's participants.
// DOCSTART 10 // DOCSTART 10
Set<Party> additionalParties = Collections.singleton(regulator); Set<Party> additionalParties = singleton(regulator);
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker())); SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker()));
// DOCEND 10 // DOCEND 10

View File

@ -15,7 +15,7 @@ import net.corda.core.utilities.ProgressTracker;
import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID; import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID;
// Replace TemplateFlow's definition with: // Replace Initiator's definition with:
@InitiatingFlow @InitiatingFlow
@StartableByRPC @StartableByRPC
public class IOUFlow extends FlowLogic<Void> { public class IOUFlow extends FlowLogic<Void> {
@ -44,7 +44,7 @@ public class IOUFlow extends FlowLogic<Void> {
@Override @Override
public Void call() throws FlowException { public Void call() throws FlowException {
// We retrieve the notary identity from the network map. // We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We create the transaction components. // We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty); IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
@ -52,12 +52,12 @@ public class IOUFlow extends FlowLogic<Void> {
Command cmd = new Command<>(cmdType, getOurIdentity().getOwningKey()); Command cmd = new Command<>(cmdType, getOurIdentity().getOwningKey());
// We create a transaction builder and add the components. // We create a transaction builder and add the components.
final TransactionBuilder txBuilder = new TransactionBuilder(notary) TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, TEMPLATE_CONTRACT_ID) .addOutputState(outputState, TEMPLATE_CONTRACT_ID)
.addCommand(cmd); .addCommand(cmd);
// Signing the transaction. // Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Finalising the transaction. // Finalising the transaction.
subFlow(new FinalityFlow(signedTx)); subFlow(new FinalityFlow(signedTx));

View File

@ -43,11 +43,11 @@ public class IOUFlow extends FlowLogic<Void> {
@Override @Override
public Void call() throws FlowException { public Void call() throws FlowException {
// We retrieve the notary identity from the network map. // We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// DOCSTART 02 // DOCSTART 02
// We create a transaction builder. // We create a transaction builder.
final TransactionBuilder txBuilder = new TransactionBuilder(); TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary); txBuilder.setNotary(notary);
// We create the transaction components. // We create the transaction components.
@ -63,7 +63,7 @@ public class IOUFlow extends FlowLogic<Void> {
txBuilder.verify(getServiceHub()); txBuilder.verify(getServiceHub());
// Signing the transaction. // Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Creating a session with the other party. // Creating a session with the other party.
FlowSession otherPartySession = initiateFlow(otherParty); FlowSession otherPartySession = initiateFlow(otherParty);

View File

@ -18,7 +18,7 @@ import net.corda.core.utilities.ProgressTracker
import com.template.TemplateContract.TEMPLATE_CONTRACT_ID import com.template.TemplateContract.TEMPLATE_CONTRACT_ID
// Replace TemplateFlow's definition with: // Replace Initiator's definition with:
@InitiatingFlow @InitiatingFlow
@StartableByRPC @StartableByRPC
class IOUFlow(val iouValue: Int, class IOUFlow(val iouValue: Int,

View File

@ -14,7 +14,6 @@ import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
// DOCEND 01 // DOCEND 01
@InitiatingFlow @InitiatingFlow

View File

@ -1,5 +1,5 @@
Getting set up Getting set up for CorDapp development
============== ======================================
Software requirements Software requirements
--------------------- ---------------------

View File

@ -1,15 +1,6 @@
Quickstart Quickstart
========== ==========
.. only:: pdfmode
.. toctree::
:caption: Other docs
:maxdepth: 1
getting-set-up.rst
tutorial-cordapp.rst
Welcome to the Corda Quickstart Guide. Follow the links below to help get going quickly with Corda. Welcome to the Corda Quickstart Guide. Follow the links below to help get going quickly with Corda.
I want to: I want to:

View File

@ -4,8 +4,8 @@
<script type="text/javascript" src="_static/jquery.js"></script> <script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script> <script type="text/javascript" src="_static/codesets.js"></script>
The example CorDapp Running the example CorDapp
=================== ===========================
.. contents:: .. contents::

View File

@ -1,5 +1,5 @@
CorDapp structure Structuring a CorDapp
================= =====================
.. contents:: .. contents::

View File

@ -68,8 +68,8 @@ dependencies {
// JOptSimple: command line option parsing // JOptSimple: command line option parsing
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
// FastClasspathScanner: classpath scanning // ClassGraph: classpath scanning
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" compile "io.github.classgraph:classgraph:$class_graph_version"
compile "commons-io:commons-io:$commonsio_version" compile "commons-io:commons-io:$commonsio_version"
compile "com.spotify:docker-client:$docker_client_version" compile "com.spotify:docker-client:$docker_client_version"

View File

@ -1,27 +1,29 @@
package net.corda.behave.scenarios package net.corda.behave.scenarios
import cucumber.api.java8.En import cucumber.api.java8.En
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.classgraph.ClassGraph
import net.corda.behave.scenarios.api.StepsBlock import net.corda.behave.scenarios.api.StepsBlock
import net.corda.behave.scenarios.api.StepsProvider import net.corda.behave.scenarios.api.StepsProvider
import net.corda.behave.scenarios.steps.* import net.corda.behave.scenarios.steps.*
import net.corda.core.internal.objectOrNewInstance import net.corda.core.internal.objectOrNewInstance
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.contextLogger
@Suppress("KDocMissingDocumentation") @Suppress("KDocMissingDocumentation")
class StepsContainer(val state: ScenarioState) : En { class StepsContainer(val state: ScenarioState) : En {
companion object { companion object {
private val log = contextLogger()
val stepsProviders: List<StepsProvider> by lazy { val stepsProviders: List<StepsProvider> by lazy {
FastClasspathScanner().addClassLoader(this::class.java.classLoader).scan() ClassGraph()
.getNamesOfClassesImplementing(StepsProvider::class.java) .addClassLoader(this::class.java.classLoader)
.mapNotNull { this::class.java.classLoader.loadClass(it).asSubclass(StepsProvider::class.java) } .enableAllInfo()
.scan()
.use { it.getClassesImplementing(StepsProvider::class.java.name).loadClasses(StepsProvider::class.java) }
.map { it.kotlin.objectOrNewInstance() } .map { it.kotlin.objectOrNewInstance() }
} }
} }
private val log = loggerFor<StepsContainer>()
private val stepDefinitions: List<StepsBlock> = listOf( private val stepDefinitions: List<StepsBlock> = listOf(
CashSteps(), CashSteps(),
ConfigurationSteps(), ConfigurationSteps(),

View File

@ -44,8 +44,8 @@ dependencies {
shadow "org.apache.curator:curator-recipes:${curator_version}" shadow "org.apache.curator:curator-recipes:${curator_version}"
testCompile "org.apache.curator:curator-test:${curator_version}" testCompile "org.apache.curator:curator-test:${curator_version}"
// FastClasspathScanner: classpath scanning - needed for the NetworkBootstrapper. // ClassGraph: classpath scanning
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" compile "io.github.classgraph:classgraph:$class_graph_version"
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version" compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"

View File

@ -1,6 +1,6 @@
package net.corda.nodeapi.internal package net.corda.nodeapi.internal
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.classgraph.ClassGraph
import net.corda.core.contracts.Contract import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.UpgradedContract import net.corda.core.contracts.UpgradedContract
@ -28,15 +28,13 @@ class ContractsJarFile(private val file: Path) : ContractsJar {
override val hash: SecureHash by lazy(LazyThreadSafetyMode.NONE, file::hash) override val hash: SecureHash by lazy(LazyThreadSafetyMode.NONE, file::hash)
override fun scan(): List<ContractClassName> { override fun scan(): List<ContractClassName> {
val scanResult = FastClasspathScanner() val scanResult = ClassGraph().overrideClasspath(singleton(file)).enableAllInfo().scan()
// A set of a single element may look odd, but if this is removed "Path" which itself is an `Iterable`
// is getting broken into pieces to scan individually, which doesn't yield desired effect.
.overrideClasspath(singleton(file))
.scan()
val contractClassNames = coreContractClasses val contractClassNames = scanResult.use {
.flatMap { scanResult.getNamesOfClassesImplementing(it.qualifiedName) } coreContractClasses
.flatMap { scanResult.getClassesImplementing(it.qualifiedName).names }
.toSet() .toSet()
}
return URLClassLoader(arrayOf(file.toUri().toURL()), Contract::class.java.classLoader).use { cl -> return URLClassLoader(arrayOf(file.toUri().toURL()), Contract::class.java.classLoader).use { cl ->
contractClassNames.mapNotNull { contractClassNames.mapNotNull {

View File

@ -15,7 +15,6 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.JarScanningCordappLoader
@ -25,16 +24,13 @@ import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.internal.TestCordappDirectories
import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.getTimestampAsDirectoryName
import net.corda.testing.node.internal.packageInDirectory
import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.services.MockAttachmentStorage
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
class AttachmentsClassLoaderStaticContractTests { class AttachmentsClassLoaderStaticContractTests {
private companion object { private companion object {
@ -101,22 +97,11 @@ class AttachmentsClassLoaderStaticContractTests {
@Test @Test
fun `verify that contract DummyContract is in classPath`() { fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract") val contractClass = Class.forName("net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract")
val contract = contractClass.newInstance() as Contract assertThat(contractClass.newInstance()).isInstanceOf(Contract::class.java)
assertNotNull(contract)
} }
private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader { private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader {
val dirs = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) }
val cordapps = cordappsForPackages(packages) return JarScanningCordappLoader.fromDirectories(dirs)
return testDirectory().let { directory ->
cordapps.packageInDirectory(directory)
JarScanningCordappLoader.fromDirectories(listOf(directory), VersionInfo.UNKNOWN)
}
}
private fun testDirectory(): Path {
return Paths.get("build", getTimestampAsDirectoryName())
} }
} }

View File

@ -4,19 +4,18 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.* import net.corda.core.flows.*
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
import net.corda.core.internal.packageName
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.internal.toDatabaseSchemaName
import org.junit.ClassRule import org.junit.ClassRule
import net.corda.testing.node.internal.cordappForClasses
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -54,23 +53,18 @@ class AsymmetricCorDappsTests : IntegrationTest() {
} }
@Test @Test
fun noSharedCorDappsWithAsymmetricSpecificClasses() { fun `no shared cordapps with asymmetric specific classes`() {
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) { driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) {
val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java))).getOrThrow()
val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java)))).getOrThrow() val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java, Pong::class.java))).getOrThrow()
val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java, Pong::class.java)))).getOrThrow()
nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow()
} }
} }
@Test @Test
fun sharedCorDappsWithAsymmetricSpecificClasses() { fun `shared cordapps with asymmetric specific classes`() {
val sharedCordapp = cordappForClasses(Ping::class.java)
val resourceName = "cordapp.properties" val cordappForNodeB = cordappForClasses(Pong::class.java)
val cordappPropertiesResource = this::class.java.getResource(resourceName)
val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource)
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) { driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) {
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
@ -79,12 +73,9 @@ class AsymmetricCorDappsTests : IntegrationTest() {
} }
@Test @Test
fun sharedCorDappsWithAsymmetricSpecificClassesInProcess() { fun `shared cordapps with asymmetric specific classes in process`() {
val sharedCordapp = cordappForClasses(Ping::class.java)
val resourceName = "cordapp.properties" val cordappForNodeB = cordappForClasses(Pong::class.java)
val cordappPropertiesResource = this::class.java.getResource(resourceName)
val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource)
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) { driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) {
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()

View File

@ -1,8 +1,9 @@
package net.corda.node.flows package net.corda.node.flows
import net.corda.client.rpc.CordaRPCClient import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.list import net.corda.core.internal.list
import net.corda.core.internal.moveTo
import net.corda.core.internal.readLines import net.corda.core.internal.readLines
import net.corda.core.messaging.startTrackedFlow import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
@ -13,17 +14,26 @@ import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testMessage.Message import net.corda.testMessage.Message
import net.corda.testMessage.MessageState import net.corda.testMessage.MessageState
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testMessage.*
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.User import net.corda.testing.node.TestCordapp
import net.corda.testing.node.internal.ListenProcessDeathException import net.corda.testing.node.internal.ListenProcessDeathException
import net.corda.testing.node.internal.TestCordappDirectories
import net.corda.testing.node.internal.cordappForClasses
import net.test.cordapp.v1.Record
import net.test.cordapp.v1.SendMessageFlow import net.test.cordapp.v1.SendMessageFlow
import org.junit.ClassRule import org.junit.ClassRule
import org.junit.Test import org.junit.Test
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -36,125 +46,111 @@ class FlowCheckpointVersionNodeStartupCheckTest: IntegrationTest() {
val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName(), DUMMY_NOTARY_NAME.toDatabaseSchemaName()) val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName(), DUMMY_NOTARY_NAME.toDatabaseSchemaName())
val message = Message("Hello world!") val message = Message("Hello world!")
val classes = setOf(net.corda.testMessage.MessageState::class.java, val defaultCordapp = cordappForClasses(
net.corda.testMessage.MessageContract::class.java, MessageState::class.java,
net.test.cordapp.v1.SendMessageFlow::class.java, MessageContract::class.java,
net.corda.testMessage.MessageSchema::class.java, SendMessageFlow::class.java,
net.corda.testMessage.MessageSchemaV1::class.java, MessageSchema::class.java,
net.test.cordapp.v1.Record::class.java) MessageSchemaV1::class.java,
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery"), invokeRpc("vaultTrack"))) Record::class.java
)
} }
@Test @Test
fun `restart node successfully with suspended flow`() { fun `restart node successfully with suspended flow`() {
return driver(parametersForRestartingNodes(listOf(defaultCordapp))) {
val cordapps = setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)) createSuspendedFlowInBob(cordapps = emptySet())
return driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, cordappsForAllNodes = cordapps)) {
{
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME).getOrThrow()
val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME).getOrThrow()
alice.stop()
CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use {
val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
//wait until Bob progresses as far as possible because alice node is off
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
}
bob.stop()
}()
val result = {
// Bob will resume the flow // Bob will resume the flow
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() val alice = startNode(providedName = ALICE_NAME).getOrThrow()
startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false)).getOrThrow() startNode(providedName = BOB_NAME).getOrThrow()
CordaRPCClient(alice.rpcAddress).start(user.username, user.password).use { val page = alice.rpc.vaultTrack(MessageState::class.java)
val page = it.proxy.vaultTrack(MessageState::class.java) val result = if (page.snapshot.states.isNotEmpty()) {
if (page.snapshot.states.isNotEmpty()) {
page.snapshot.states.first() page.snapshot.states.first()
} else { } else {
val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single() val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single()
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first() if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
} }
}
}()
assertNotNull(result) assertNotNull(result)
assertEquals(message, result.state.data.message) assertEquals(message, result.state.data.message)
} }
} }
private fun assertNodeRestartFailure( @Test
cordapps: Set<TestCorDapp>?, fun `restart node with incompatible version of suspended flow due to different jar name`() {
cordappsVersionAtStartup: Set<TestCorDapp>, driver(parametersForRestartingNodes()) {
cordappsVersionAtRestart: Set<TestCorDapp>, val cordapp = defaultCordapp.withName("different-jar-name-test-${UUID.randomUUID()}")
reuseAdditionalCordappsAtRestart: Boolean, // Create the CorDapp jar file manually first to get hold of the directory that will contain it so that we can
assertNodeLogs: String // rename the filename later. The cordappDir, which acts as pointer to the jar file, does not get renamed.
) { val cordappDir = TestCordappDirectories.getJarDirectory(cordapp)
val cordappJar = cordappDir.list().single()
return driver(DriverParameters( createSuspendedFlowInBob(setOf(cordapp))
startNodesInProcess = false, // start nodes in separate processes to ensure CordappLoader is not shared between restarts
inMemoryDB = false, // ensure database is persisted between node restarts so we can keep suspended flow in Bob's node // Rename the jar file. TestCordappDirectories caches the location of the jar file but the use of the random
cordappsForAllNodes = cordapps) // UUID in the name means there's zero chance of contaminating another test.
) { cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}")
val bobLogFolder = {
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow() assertBobFailsToStartWithLogMessage(
val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow() setOf(cordapp),
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message
)
}
}
@Test
fun `restart node with incompatible version of suspended flow due to different jar hash`() {
driver(parametersForRestartingNodes()) {
val originalCordapp = defaultCordapp.withName("different-jar-hash-test-${UUID.randomUUID()}")
val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single()
createSuspendedFlowInBob(setOf(originalCordapp))
// The vendor is part of the MANIFEST so changing it is sufficient to change the jar hash
val modifiedCordapp = originalCordapp.withVendor("${originalCordapp.vendor}-modified")
val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single()
modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING)
assertBobFailsToStartWithLogMessage(
setOf(originalCordapp),
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of"
)
}
}
private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set<TestCordapp>) {
val (alice, bob) = listOf(ALICE_NAME, BOB_NAME)
.map { startNode(providedName = it, additionalCordapps = cordapps) }
.transpose()
.getOrThrow()
alice.stop() alice.stop()
CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use { val flowTracker = bob.rpc.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress // Wait until Bob progresses as far as possible because Alice node is offline
// wait until Bob progresses as far as possible because Alice node is offline
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
}
val logFolder = bob.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
// SendMessageFlow suspends in Bob node
bob.stop() bob.stop()
logFolder }
}()
startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false),
additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow()
private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) {
assertFailsWith(ListenProcessDeathException::class) { assertFailsWith(ListenProcessDeathException::class) {
startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false), startNode(
additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow() providedName = BOB_NAME,
customOverrides = mapOf("devMode" to false),
additionalCordapps = cordapps,
regenerateCordappsOnStart = true
).getOrThrow()
} }
val logFile = bobLogFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() } val logDir = baseDirectory(BOB_NAME) / NodeStartup.LOGS_DIRECTORY_NAME
val numberOfNodesThatLogged = logFile.readLines { it.filter { assertNodeLogs in it }.count() } val logFile = logDir.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }
assertEquals(1, numberOfNodesThatLogged) val matchingLineCount = logFile.readLines { it.filter { line -> logMessage in line }.count() }
} assertEquals(1, matchingLineCount)
} }
@Test private fun parametersForRestartingNodes(cordappsForAllNodes: List<TestCordapp> = emptyList()): DriverParameters {
fun `restart nodes with incompatible version of suspended flow due to different jar name`() { return DriverParameters(
startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts
assertNodeRestartFailure( inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows
emptySet(), cordappsForAllNodes = cordappsForAllNodes
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), )
setOf(TestCorDapp.Factory.create("testJar2", "1.0", classes = classes)),
false,
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message)
}
@Test
fun `restart nodes with incompatible version of suspended flow`() {
assertNodeRestartFailure(
emptySet(),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes + net.test.cordapp.v1.SendMessageFlow::class.java)),
false,
// the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of")
}
@Test
fun `restart nodes with incompatible version of suspended flow due to different timestamps only`() {
assertNodeRestartFailure(
emptySet(),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
false,
// the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of")
} }
} }

View File

@ -7,6 +7,8 @@ import net.corda.testMessage.MessageContract
import net.corda.testMessage.MessageState import net.corda.testMessage.MessageState
import net.corda.RpcInfo import net.corda.RpcInfo
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.RpcInfo
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FinalityFlow
@ -25,6 +27,10 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.all
import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID
import net.corda.testMessage.Message
import net.corda.testMessage.MessageContract
import net.corda.testMessage.MessageState
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
@ -67,7 +73,11 @@ class FlowsDrainingModeContentionTest : IntegrationTest() {
@Test @Test
fun `draining mode does not deadlock with acks between 2 nodes`() { fun `draining mode does not deadlock with acks between 2 nodes`() {
val message = "Ground control to Major Tom" val message = "Ground control to Major Tom"
driver(DriverParameters(startNodesInProcess = true, portAllocation = portAllocation, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) { driver(DriverParameters(
startNodesInProcess = true,
portAllocation = portAllocation,
extraCordappPackagesToScan = listOf(MessageState::class.packageName)
)) {
val nodeA = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow() val nodeA = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow()
val nodeB = startNode(providedName = BOB_NAME, rpcUsers = users).getOrThrow() val nodeB = startNode(providedName = BOB_NAME, rpcUsers = users).getOrThrow()
@ -84,11 +94,12 @@ class FlowsDrainingModeContentionTest : IntegrationTest() {
@StartableByRPC @StartableByRPC
@InitiatingFlow @InitiatingFlow
class ProposeTransactionAndWaitForCommit(private val data: String, private val myRpcInfo: RpcInfo, private val counterParty: Party, private val notary: Party) : FlowLogic<SignedTransaction>() { class ProposeTransactionAndWaitForCommit(private val data: String,
private val myRpcInfo: RpcInfo,
private val counterParty: Party,
private val notary: Party) : FlowLogic<SignedTransaction>() {
@Suspendable @Suspendable
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
val session = initiateFlow(counterParty) val session = initiateFlow(counterParty)
val messageState = MessageState(message = Message(data), by = ourIdentity) val messageState = MessageState(message = Message(data), by = ourIdentity)
val command = Command(MessageContract.Commands.Send(), messageState.participants.map { it.owningKey }) val command = Command(MessageContract.Commands.Send(), messageState.participants.map { it.owningKey })
@ -105,10 +116,8 @@ class ProposeTransactionAndWaitForCommit(private val data: String, private val m
@InitiatedBy(ProposeTransactionAndWaitForCommit::class) @InitiatedBy(ProposeTransactionAndWaitForCommit::class)
class SignTransactionTriggerDrainingModeAndFinality(private val session: FlowSession) : FlowLogic<Unit>() { class SignTransactionTriggerDrainingModeAndFinality(private val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
val tx = subFlow(ReceiveTransactionFlow(session)) val tx = subFlow(ReceiveTransactionFlow(session))
val signedTx = serviceHub.addSignature(tx) val signedTx = serviceHub.addSignature(tx)
val initiatingRpcInfo = session.receive<RpcInfo>().unwrap { it } val initiatingRpcInfo = session.receive<RpcInfo>().unwrap { it }
@ -119,7 +128,6 @@ class SignTransactionTriggerDrainingModeAndFinality(private val session: FlowSes
} }
private fun triggerDrainingModeForInitiatingNode(initiatingRpcInfo: RpcInfo) { private fun triggerDrainingModeForInitiatingNode(initiatingRpcInfo: RpcInfo) {
CordaRPCClient(initiatingRpcInfo.address).start(initiatingRpcInfo.username, initiatingRpcInfo.password).use { CordaRPCClient(initiatingRpcInfo.address).start(initiatingRpcInfo.username, initiatingRpcInfo.password).use {
it.proxy.setFlowsDrainingModeEnabled(true) it.proxy.setFlowsDrainingModeEnabled(true)
} }

View File

@ -8,7 +8,6 @@ import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
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.toLedgerTransaction import net.corda.core.internal.toLedgerTransaction
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
@ -16,7 +15,6 @@ import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderImpl
@ -24,8 +22,11 @@ import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.DriverDSL
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.internal.* import net.corda.testing.internal.*
import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.cordappsForPackages
@ -55,7 +56,6 @@ class AttachmentLoadingTests : IntegrationTest() {
val databaseSchemas = IntegrationTestSchemas(DUMMY_BANK_A_NAME.toDatabaseSchemaName(), DUMMY_BANK_B_NAME.toDatabaseSchemaName(), val databaseSchemas = IntegrationTestSchemas(DUMMY_BANK_A_NAME.toDatabaseSchemaName(), DUMMY_BANK_B_NAME.toDatabaseSchemaName(),
DUMMY_NOTARY_NAME.toDatabaseSchemaName()) DUMMY_NOTARY_NAME.toDatabaseSchemaName())
private val logger = contextLogger()
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!! val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
const val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract" const val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
@ -66,12 +66,6 @@ class AttachmentLoadingTests : IntegrationTest() {
.asSubclass(FlowLogic::class.java) .asSubclass(FlowLogic::class.java)
val DUMMY_BANK_A = TestIdentity(DUMMY_BANK_A_NAME, 40).party val DUMMY_BANK_A = TestIdentity(DUMMY_BANK_A_NAME, 40).party
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
private fun DriverDSL.createTwoNodes(): List<NodeHandle> {
return listOf(
startNode(providedName = bankAName),
startNode(providedName = bankBName)
).transpose().getOrThrow()
}
} }
private val services = object : ServicesForResolution { private val services = object : ServicesForResolution {

View File

@ -46,11 +46,14 @@ class ScheduledFlowIntegrationTests : IntegrationTest() {
} }
@StartableByRPC @StartableByRPC
class InsertInitialStateFlow(private val destination: Party, private val notary: Party, private val identity: Int = 1, private val scheduledFor: Instant? = null) : FlowLogic<Unit>() { class InsertInitialStateFlow(private val destination: Party,
private val notary: Party,
private val identity: Int = 1,
private val scheduledFor: Instant? = null) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
val scheduledState = ScheduledState(scheduledFor val creationTime = scheduledFor ?: serviceHub.clock.instant()
?: serviceHub.clock.instant(), ourIdentity, destination, identity.toString()) val scheduledState = ScheduledState(creationTime, ourIdentity, destination, identity.toString())
val builder = TransactionBuilder(notary) val builder = TransactionBuilder(notary)
.addOutputState(scheduledState, DummyContract.PROGRAM_ID) .addOutputState(scheduledState, DummyContract.PROGRAM_ID)
.addCommand(dummyCommand(ourIdentity.owningKey)) .addCommand(dummyCommand(ourIdentity.owningKey))
@ -104,8 +107,20 @@ class ScheduledFlowIntegrationTests : IntegrationTest() {
val scheduledFor = Instant.now().plusSeconds(10) val scheduledFor = Instant.now().plusSeconds(10)
val initialiseFutures = mutableListOf<CordaFuture<*>>() val initialiseFutures = mutableListOf<CordaFuture<*>>()
for (i in 0 until N) { for (i in 0 until N) {
initialiseFutures.add(aliceClient.proxy.startFlow(::InsertInitialStateFlow, bob.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i, scheduledFor).returnValue) initialiseFutures.add(aliceClient.proxy.startFlow(
initialiseFutures.add(bobClient.proxy.startFlow(::InsertInitialStateFlow, alice.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i + 100, scheduledFor).returnValue) ::InsertInitialStateFlow,
bob.nodeInfo.legalIdentities.first(),
defaultNotaryIdentity,
i,
scheduledFor
).returnValue)
initialiseFutures.add(bobClient.proxy.startFlow(
::InsertInitialStateFlow,
alice.nodeInfo.legalIdentities.first(),
defaultNotaryIdentity,
i + 100,
scheduledFor
).returnValue)
} }
initialiseFutures.getOrThrowAll() initialiseFutures.getOrThrowAll()

View File

@ -46,12 +46,12 @@ class SendMessageFlow(private val message: Message, private val notary: Party, p
progressTracker.currentStep = FINALISING_TRANSACTION progressTracker.currentStep = FINALISING_TRANSACTION
if (reciepent != null) { return if (reciepent != null) {
val session = initiateFlow(reciepent) val session = initiateFlow(reciepent)
subFlow(SendTransactionFlow(session, signedTx)) subFlow(SendTransactionFlow(session, signedTx))
return subFlow(FinalityFlow(signedTx, setOf(reciepent), FINALISING_TRANSACTION.childProgressTracker())) subFlow(FinalityFlow(signedTx, setOf(reciepent), FINALISING_TRANSACTION.childProgressTracker()))
} else { } else {
return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker())) subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker()))
} }
} }
} }
@ -59,7 +59,6 @@ class SendMessageFlow(private val message: Message, private val notary: Party, p
@InitiatedBy(SendMessageFlow::class) @InitiatedBy(SendMessageFlow::class)
class Record(private val session: FlowSession) : FlowLogic<Unit>() { class Record(private val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
val tx = subFlow(ReceiveTransactionFlow(session, statesToRecord = StatesToRecord.ALL_VISIBLE)) val tx = subFlow(ReceiveTransactionFlow(session, statesToRecord = StatesToRecord.ALL_VISIBLE))

View File

@ -1,7 +1,7 @@
package net.corda.node.internal.cordapp package net.corda.node.internal.cordapp
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.classgraph.ClassGraph
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult import io.github.classgraph.ScanResult
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
@ -62,11 +62,13 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
/** /**
* Creates a CordappLoader from multiple directories. * Creates a CordappLoader from multiple directories.
* *
* @param corDappDirectories Directories used to scan for CorDapp JARs. * @param cordappDirs Directories used to scan for CorDapp JARs.
*/ */
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader { fun fromDirectories(cordappDirs: Collection<Path>,
logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}") versionInfo: VersionInfo = VersionInfo.UNKNOWN,
val paths = corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() } extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps) return JarScanningCordappLoader(paths, versionInfo, extraCordapps)
} }
@ -96,7 +98,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
} }
private fun loadCordapps(): List<CordappImpl> { private fun loadCordapps(): List<CordappImpl> {
val cordapps = cordappJarPaths val cordapps = cordappJarPaths
.map { scanCordapp(it).toCordapp(it) } .map { url -> scanCordapp(url).use { it.toCordapp(url) } }
.filter { .filter {
if (it.info.minimumPlatformVersion > versionInfo.platformVersion) { if (it.info.minimumPlatformVersion > versionInfo.platformVersion) {
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " + logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " +
@ -204,7 +206,8 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult { private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
logger.info("Scanning CorDapp in ${cordappJarPath.url}") logger.info("Scanning CorDapp in ${cordappJarPath.url}")
return cachedScanResult.computeIfAbsent(cordappJarPath) { return cachedScanResult.computeIfAbsent(cordappJarPath) {
RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix) val scanResult = ClassGraph().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).enableAllInfo().scan()
RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix)
} }
} }
@ -241,40 +244,49 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
return map { it.kotlin.objectOrNewInstance() } return map { it.kotlin.objectOrNewInstance() }
} }
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) { private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) : AutoCloseable {
fun getNamesOfClassesImplementing(type: KClass<*>): List<String> { fun getNamesOfClassesImplementing(type: KClass<*>): List<String> {
return scanResult.getNamesOfClassesImplementing(type.java) return scanResult.getClassesImplementing(type.java.name).names.filter { it.startsWith(qualifiedNamePrefix) }
.filter { it.startsWith(qualifiedNamePrefix) }
} }
fun <T : Any> getClassesWithSuperclass(type: KClass<T>): List<Class<out T>> { fun <T : Any> getClassesWithSuperclass(type: KClass<T>): List<Class<out T>> {
return scanResult.getNamesOfSubclassesOf(type.java) return scanResult
.getSubclasses(type.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) } .filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) } .mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) } .filterNot { it.isAbstractClass }
} }
fun <T : Any> getClassesImplementing(type: KClass<T>): List<T> { fun <T : Any> getClassesImplementing(type: KClass<T>): List<T> {
return scanResult.getNamesOfClassesImplementing(type.java) return scanResult
.getClassesImplementing(type.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) } .filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) } .mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) } .filterNot { it.isAbstractClass }
.map { it.kotlin.objectOrNewInstance() } .map { it.kotlin.objectOrNewInstance() }
} }
fun <T : Any> getClassesWithAnnotation(type: KClass<T>, annotation: KClass<out Annotation>): List<Class<out T>> { fun <T : Any> getClassesWithAnnotation(type: KClass<T>, annotation: KClass<out Annotation>): List<Class<out T>> {
return scanResult.getNamesOfClassesWithAnnotation(annotation.java) return scanResult
.getClassesWithAnnotation(annotation.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) } .filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) } .mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) } .filterNot { Modifier.isAbstract(it.modifiers) }
} }
fun <T : Any> getConcreteClassesOfType(type: KClass<T>): List<Class<out T>> { fun <T : Any> getConcreteClassesOfType(type: KClass<T>): List<Class<out T>> {
return scanResult.getNamesOfSubclassesOf(type.java) return scanResult
.getSubclasses(type.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) } .filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) } .mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) } .filterNot { it.isAbstractClass }
} }
override fun close() = scanResult.close()
} }
} }

View File

@ -5,7 +5,7 @@ import net.corda.core.internal.cordapp.CordappImpl.Info.Companion.UNKNOWN_VALUE
import java.util.jar.Attributes import java.util.jar.Attributes
import java.util.jar.Manifest import java.util.jar.Manifest
fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest { fun createTestManifest(name: String, title: String, version: String, vendor: String, targetVersion: Int): Manifest {
val manifest = Manifest() val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped. // Mandatory manifest attribute. If not present, all other entries are silently skipped.
@ -20,6 +20,7 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str
manifest["Implementation-Title"] = title manifest["Implementation-Title"] = title
manifest["Implementation-Version"] = version manifest["Implementation-Version"] = version
manifest["Implementation-Vendor"] = vendor manifest["Implementation-Vendor"] = vendor
manifest["Target-Platform-Version"] = targetVersion.toString()
return manifest return manifest
} }

View File

@ -2,14 +2,12 @@ package net.corda.node.internal.cordapp
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.internal.packageName
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader import net.corda.testing.node.internal.TestCordappDirectories
import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.cordappForPackages
import net.corda.testing.node.internal.getTimestampAsDirectoryName
import net.corda.testing.node.internal.packageInDirectory
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
@InitiatingFlow @InitiatingFlow
@ -38,7 +36,6 @@ class DummyRPCFlow : FlowLogic<Unit>() {
class JarScanningCordappLoaderTest { class JarScanningCordappLoaderTest {
private companion object { private companion object {
const val testScanPackage = "net.corda.node.internal.cordapp"
const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract" const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract"
const val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator" const val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator"
} }
@ -70,32 +67,18 @@ class JarScanningCordappLoaderTest {
@Test @Test
fun `flows are loaded by loader`() { fun `flows are loaded by loader`() {
val loader = cordappLoaderForPackages(listOf(testScanPackage)) val dir = TestCordappDirectories.getJarDirectory(cordappForPackages(javaClass.packageName))
val loader = JarScanningCordappLoader.fromDirectories(listOf(dir))
val actual = loader.cordapps.toTypedArray()
// One cordapp from this source tree. In gradle it will also pick up the node jar. // One cordapp from this source tree. In gradle it will also pick up the node jar.
assertThat(actual.size == 0 || actual.size == 1).isTrue() assertThat(loader.cordapps).isNotEmpty
val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() } val actualCordapp = loader.cordapps.single { !it.initiatedFlows.isEmpty() }
assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java) assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java)
assertThat(actualCordapp.rpcFlows).first().hasSameClassAs(DummyRPCFlow::class.java) assertThat(actualCordapp.rpcFlows).first().hasSameClassAs(DummyRPCFlow::class.java)
assertThat(actualCordapp.schedulableFlows).first().hasSameClassAs(DummySchedulableFlow::class.java) assertThat(actualCordapp.schedulableFlows).first().hasSameClassAs(DummySchedulableFlow::class.java)
} }
@Test
fun `duplicate packages are ignored`() {
val loader = cordappLoaderForPackages(listOf(testScanPackage, testScanPackage))
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
assertThat(cordapps).hasSize(1)
}
@Test
fun `sub-packages are ignored`() {
val loader = cordappLoaderForPackages(listOf("net.corda.core", testScanPackage))
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
assertThat(cordapps).hasSize(1)
}
// This test exists because the appClassLoader is used by serialisation and we need to ensure it is the classloader // This test exists because the appClassLoader is used by serialisation and we need to ensure it is the classloader
// being used internally. Later iterations will use a classloader per cordapp and this test can be retired. // being used internally. Later iterations will use a classloader per cordapp and this test can be retired.
@Test @Test
@ -159,16 +142,4 @@ class JarScanningCordappLoaderTest {
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2)) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
assertThat(loader.cordapps).hasSize(1) assertThat(loader.cordapps).hasSize(1)
} }
private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader {
val cordapps = cordappsForPackages(packages)
return testDirectory().let { directory ->
cordapps.packageInDirectory(directory)
JarScanningCordappLoader.fromDirectories(listOf(directory))
}
}
private fun testDirectory(): Path {
return Paths.get("build", getTimestampAsDirectoryName())
}
} }

View File

@ -8,8 +8,8 @@ import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.flows.SchedulableFlow import net.corda.core.flows.SchedulableFlow
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
@ -28,6 +28,9 @@ import kotlin.reflect.jvm.jvmName
import kotlin.test.fail import kotlin.test.fail
class ScheduledFlowsDrainingModeTest { class ScheduledFlowsDrainingModeTest {
companion object {
private val logger = contextLogger()
}
private lateinit var mockNet: InternalMockNetwork private lateinit var mockNet: InternalMockNetwork
private lateinit var aliceNode: TestStartedNode private lateinit var aliceNode: TestStartedNode
@ -38,10 +41,6 @@ class ScheduledFlowsDrainingModeTest {
private var executor: ScheduledExecutorService? = null private var executor: ScheduledExecutorService? = null
companion object {
private val logger = loggerFor<ScheduledFlowsDrainingModeTest>()
}
@Before @Before
fun setup() { fun setup() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true) mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true)
@ -61,7 +60,6 @@ class ScheduledFlowsDrainingModeTest {
@Test @Test
fun `flows draining mode ignores scheduled flows until unset`() { fun `flows draining mode ignores scheduled flows until unset`() {
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
var shouldFail = true var shouldFail = true
@ -73,7 +71,8 @@ class ScheduledFlowsDrainingModeTest {
.map { update -> update.produced.single().state.data as ScheduledState } .map { update -> update.produced.single().state.data as ScheduledState }
scheduledStates.filter { state -> !state.processed }.doOnNext { _ -> scheduledStates.filter { state -> !state.processed }.doOnNext { _ ->
// this is needed because there is a delay between the moment a SchedulableState gets in the Vault and the first time nextScheduledActivity is called // This is needed because there is a delay between the moment a SchedulableState gets in the Vault and the
// first time nextScheduledActivity is called
executor!!.schedule({ executor!!.schedule({
logger.info("Disabling flows draining mode") logger.info("Disabling flows draining mode")
shouldFail = false shouldFail = false
@ -96,8 +95,11 @@ class ScheduledFlowsDrainingModeTest {
latch.await() latch.await()
} }
data class ScheduledState(private val creationTime: Instant, val source: Party, val destination: Party, val processed: Boolean = false, override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState { data class ScheduledState(private val creationTime: Instant,
val source: Party,
val destination: Party,
val processed: Boolean = false,
override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState {
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? { override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
return if (!processed) { return if (!processed) {
val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.jvmName, thisStateRef) val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.jvmName, thisStateRef)
@ -111,12 +113,12 @@ class ScheduledFlowsDrainingModeTest {
} }
class InsertInitialStateFlow(private val destination: Party, private val notary: Party) : FlowLogic<Unit>() { class InsertInitialStateFlow(private val destination: Party, private val notary: Party) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
val scheduledState = ScheduledState(serviceHub.clock.instant(), ourIdentity, destination) val scheduledState = ScheduledState(serviceHub.clock.instant(), ourIdentity, destination)
val builder = TransactionBuilder(notary).addOutputState(scheduledState, DummyContract.PROGRAM_ID).addCommand(dummyCommand(ourIdentity.owningKey)) val builder = TransactionBuilder(notary)
.addOutputState(scheduledState, DummyContract.PROGRAM_ID)
.addCommand(dummyCommand(ourIdentity.owningKey))
val tx = serviceHub.signInitialTransaction(builder) val tx = serviceHub.signInitialTransaction(builder)
subFlow(FinalityFlow(tx)) subFlow(FinalityFlow(tx))
} }
@ -124,10 +126,8 @@ class ScheduledFlowsDrainingModeTest {
@SchedulableFlow @SchedulableFlow
class ScheduledFlow(private val stateRef: StateRef) : FlowLogic<Unit>() { class ScheduledFlow(private val stateRef: StateRef) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
val state = serviceHub.toStateAndRef<ScheduledState>(stateRef) val state = serviceHub.toStateAndRef<ScheduledState>(stateRef)
val scheduledState = state.state.data val scheduledState = state.state.data
// Only run flow over states originating on this node // Only run flow over states originating on this node
@ -137,7 +137,10 @@ class ScheduledFlowsDrainingModeTest {
require(!scheduledState.processed) { "State should not have been previously processed" } require(!scheduledState.processed) { "State should not have been previously processed" }
val notary = state.state.notary val notary = state.state.notary
val newStateOutput = scheduledState.copy(processed = true) val newStateOutput = scheduledState.copy(processed = true)
val builder = TransactionBuilder(notary).addInputState(state).addOutputState(newStateOutput, DummyContract.PROGRAM_ID).addCommand(dummyCommand(ourIdentity.owningKey)) val builder = TransactionBuilder(notary)
.addInputState(state)
.addOutputState(newStateOutput, DummyContract.PROGRAM_ID)
.addCommand(dummyCommand(ourIdentity.owningKey))
val tx = serviceHub.signInitialTransaction(builder) val tx = serviceHub.signInitialTransaction(builder)
subFlow(FinalityFlow(tx, setOf(scheduledState.destination))) subFlow(FinalityFlow(tx, setOf(scheduledState.destination)))
} }

View File

@ -15,17 +15,13 @@ import net.corda.node.services.statemachine.StaffedFlowHospital.MedicalRecord.Ke
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.TestCorDapp import net.corda.testing.node.internal.*
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.TestStartedNode
import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Test import org.junit.Test
class FinalityHandlerTest { class FinalityHandlerTest {
private lateinit var mockNet: InternalMockNetwork private val mockNet = InternalMockNetwork()
@After @After
fun cleanUp() { fun cleanUp() {
@ -36,10 +32,7 @@ class FinalityHandlerTest {
fun `sent to flow hospital on error and attempted retry on node restart`() { fun `sent to flow hospital on error and attempted retry on node restart`() {
// Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the // Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the
// CorDapp. Bob's FinalityHandler will error when validating the tx. // CorDapp. Bob's FinalityHandler will error when validating the tx.
mockNet = InternalMockNetwork() val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(FINANCE_CORDAPP)))
val assertCordapp = TestCorDapp.Factory.create("net.corda.finance.contracts.asset", "1.0").plusPackage("net.corda.finance.contracts.asset")
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(assertCordapp)))
var bob = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME)) var bob = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
@ -87,8 +80,6 @@ class FinalityHandlerTest {
} }
private fun TestStartedNode.getTransaction(id: SecureHash): SignedTransaction? { private fun TestStartedNode.getTransaction(id: SecureHash): SignedTransaction? {
return database.transaction { return services.validatedTransactions.getTransaction(id)
services.validatedTransactions.getTransaction(id)
}
} }
} }

View File

@ -24,8 +24,11 @@ import rx.schedulers.Schedulers
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
class ServiceHubConcurrentUsageTest { class ServiceHubConcurrentUsageTest {
private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(
private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.schemas", "net.corda.node.services.vault.VaultQueryExceptionsTests", Cash::class.packageName)) "net.corda.finance.schemas",
"net.corda.node.services.vault.VaultQueryExceptionsTests",
Cash::class.packageName
))
@After @After
fun stopNodes() { fun stopNodes() {
@ -34,7 +37,6 @@ class ServiceHubConcurrentUsageTest {
@Test @Test
fun `operations requiring a transaction work from another thread`() { fun `operations requiring a transaction work from another thread`() {
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
var successful = false var successful = false
val initiatingFlow = TestFlow(mockNet.defaultNotaryIdentity) val initiatingFlow = TestFlow(mockNet.defaultNotaryIdentity)
@ -57,10 +59,8 @@ class ServiceHubConcurrentUsageTest {
} }
class TestFlow(private val notary: Party) : FlowLogic<SignedTransaction>() { class TestFlow(private val notary: Party) : FlowLogic<SignedTransaction>() {
@Suspendable @Suspendable
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
val builder = TransactionBuilder(notary) val builder = TransactionBuilder(notary)
val issuer = ourIdentity.ref(OpaqueBytes.of(0)) val issuer = ourIdentity.ref(OpaqueBytes.of(0))
Cash().generateIssue(builder, 10.DOLLARS.issuedBy(issuer), ourIdentity, notary) Cash().generateIssue(builder, 10.DOLLARS.issuedBy(issuer), ourIdentity, notary)

View File

@ -33,7 +33,6 @@ import kotlin.test.assertEquals
class ScheduledFlowTests { class ScheduledFlowTests {
companion object { companion object {
const val PAGE_SIZE = 20
val SORTING = Sort(listOf(Sort.SortColumn(SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID), Sort.Direction.DESC))) val SORTING = Sort(listOf(Sort.SortColumn(SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID), Sort.Direction.DESC)))
} }
@ -168,6 +167,7 @@ class ScheduledFlowTests {
assertTrue("Expect all states have run the scheduled task", statesFromB.all { it.state.data.processed }) assertTrue("Expect all states have run the scheduled task", statesFromB.all { it.state.data.processed })
} }
private fun queryStates(vaultService: VaultService): List<StateAndRef<ScheduledState>> = private fun queryStates(vaultService: VaultService): List<StateAndRef<ScheduledState>> {
vaultService.queryBy<ScheduledState>(VaultQueryCriteria(), sorting = SORTING).states return vaultService.queryBy<ScheduledState>(VaultQueryCriteria(), sorting = SORTING).states
}
} }

View File

@ -52,7 +52,7 @@ class NodePair(private val mockNet: InternalMockNetwork) {
@InitiatingFlow @InitiatingFlow
abstract class AbstractClientLogic<out T>(nodePair: NodePair) : FlowLogic<T>() { abstract class AbstractClientLogic<out T>(nodePair: NodePair) : FlowLogic<T>() {
protected val server = nodePair.server.info.singleIdentity() private val server = nodePair.server.info.singleIdentity()
protected abstract fun callImpl(): T protected abstract fun callImpl(): T
@Suspendable @Suspendable
override fun call() = callImpl().also { override fun call() = callImpl().also {
@ -82,9 +82,12 @@ class VaultSoftLockManagerTest {
private val mockVault = rigorousMock<VaultServiceInternal>().also { private val mockVault = rigorousMock<VaultServiceInternal>().also {
doNothing().whenever(it).softLockRelease(any(), anyOrNull()) doNothing().whenever(it).softLockRelease(any(), anyOrNull())
} }
private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(ContractImpl::class.packageName), defaultFactory = { args -> private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(ContractImpl::class.packageName), defaultFactory = { args ->
object : InternalMockNetwork.MockNode(args) { object : InternalMockNetwork.MockNode(args) {
override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, database: CordaPersistence): VaultServiceInternal { override fun makeVaultService(keyManagementService: KeyManagementService,
services: ServicesForResolution,
database: CordaPersistence): VaultServiceInternal {
val node = this val node = this
val realVault = super.makeVaultService(keyManagementService, services, database) val realVault = super.makeVaultService(keyManagementService, services, database)
return object : VaultServiceInternal by realVault { return object : VaultServiceInternal by realVault {
@ -97,13 +100,11 @@ class VaultSoftLockManagerTest {
} }
} }
}) })
private val nodePair = NodePair(mockNet) private val nodePair = NodePair(mockNet)
@After
fun tearDown() {
mockNet.stopNodes()
}
object CommandDataImpl : CommandData object CommandDataImpl : CommandData
class ClientLogic(nodePair: NodePair, val state: ContractState) : NodePair.AbstractClientLogic<List<ContractState>>(nodePair) { class ClientLogic(nodePair: NodePair, val state: ContractState) : NodePair.AbstractClientLogic<List<ContractState>>(nodePair) {
override fun callImpl() = run { override fun callImpl() = run {
subFlow(FinalityFlow(serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply { subFlow(FinalityFlow(serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply {
@ -151,6 +152,11 @@ class VaultSoftLockManagerTest {
verifyNoMoreInteractions(mockVault) verifyNoMoreInteractions(mockVault)
} }
@After
fun tearDown() {
mockNet.stopNodes()
}
@Test @Test
fun `plain old state is not soft locked`() = run(false, PlainOldState(nodePair), false) fun `plain old state is not soft locked`() = run(false, PlainOldState(nodePair), false)

View File

@ -14,10 +14,9 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
@StartableByRPC @StartableByRPC
@InitiatingFlow @InitiatingFlow
class TestCommsFlowInitiator(val x500Name: CordaX500Name? = null) : FlowLogic<List<String>>() { class TestCommsFlowInitiator(private val x500Name: CordaX500Name? = null) : FlowLogic<List<String>>() {
object SENDING : ProgressTracker.Step("SENDING") object SENDING : ProgressTracker.Step("SENDING")
object RECIEVED_ALL : ProgressTracker.Step("RECIEVED_ALL") object RECIEVED_ALL : ProgressTracker.Step("RECIEVED_ALL")
@ -42,7 +41,7 @@ class TestCommsFlowInitiator(val x500Name: CordaX500Name? = null) : FlowLogic<Li
progressTracker.currentStep = RECIEVED_ALL progressTracker.currentStep = RECIEVED_ALL
} }
val tx = TransactionBuilder(notary = serviceHub.networkMapCache.notaryIdentities.first()) val tx = TransactionBuilder(notary = serviceHub.networkMapCache.notaryIdentities.first())
tx.addOutputState(CommsTestState(responses, serviceHub.myInfo.legalIdentities.first()), CommsTestContract::class.qualifiedName!!) tx.addOutputState(CommsTestState(responses, serviceHub.myInfo.legalIdentities.first()), CommsTestContract::class.java.name)
tx.addCommand(CommsTestCommand, serviceHub.myInfo.legalIdentities.first().owningKey) tx.addCommand(CommsTestCommand, serviceHub.myInfo.legalIdentities.first().owningKey)
val signedTx = serviceHub.signInitialTransaction(tx) val signedTx = serviceHub.signInitialTransaction(tx)
subFlow(FinalityFlow(signedTx)) subFlow(FinalityFlow(signedTx))
@ -55,26 +54,22 @@ class TestCommsFlowInitiator(val x500Name: CordaX500Name? = null) : FlowLogic<Li
} }
@InitiatedBy(TestCommsFlowInitiator::class) @InitiatedBy(TestCommsFlowInitiator::class)
class TestCommsFlowResponder(val otherSideSession: FlowSession) : FlowLogic<Unit>() { class TestCommsFlowResponder(private val otherSideSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
otherSideSession.send("Hello from: " + serviceHub.myInfo.legalIdentities.first().name.toString()) otherSideSession.send("Hello from: " + serviceHub.myInfo.legalIdentities.first().name.toString())
} }
} }
@CordaSerializable @CordaSerializable
data class CommsTestState(val responses: List<String>, val issuer: AbstractParty) : ContractState { data class CommsTestState(val responses: List<String>, val issuer: AbstractParty) : ContractState {
override val participants: List<AbstractParty> override val participants: List<AbstractParty>
get() = listOf(issuer) get() = listOf(issuer)
} }
@CordaSerializable @CordaSerializable
object CommsTestCommand : CommandData object CommsTestCommand : CommandData
class CommsTestContract : Contract { class CommsTestContract : Contract {
override fun verify(tx: LedgerTransaction) { override fun verify(tx: LedgerTransaction) {
} }

View File

@ -13,7 +13,6 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
@StartableByRPC @StartableByRPC
class TestNotaryFlow : FlowLogic<String>() { class TestNotaryFlow : FlowLogic<String>() {
@ -28,38 +27,34 @@ class TestNotaryFlow : FlowLogic<String>() {
override fun call(): String { override fun call(): String {
val issueBuilder = TransactionBuilder() val issueBuilder = TransactionBuilder()
val notary = serviceHub.networkMapCache.notaryIdentities.first() val notary = serviceHub.networkMapCache.notaryIdentities.first()
issueBuilder.notary = notary; issueBuilder.notary = notary
val myIdentity = serviceHub.myInfo.legalIdentities.first() val myIdentity = serviceHub.myInfo.legalIdentities.first()
issueBuilder.addOutputState(NotaryTestState(notary.name.toString(), myIdentity), NotaryTestContract::class.qualifiedName!!) issueBuilder.addOutputState(NotaryTestState(notary.name.toString(), myIdentity), NotaryTestContract::class.java.name)
issueBuilder.addCommand(NotaryTestCommand, myIdentity.owningKey) issueBuilder.addCommand(NotaryTestCommand, myIdentity.owningKey)
val signedTx = serviceHub.signInitialTransaction(issueBuilder) val signedTx = serviceHub.signInitialTransaction(issueBuilder)
val issueResult = subFlow(FinalityFlow(signedTx)) val issueResult = subFlow(FinalityFlow(signedTx))
progressTracker.currentStep = ISSUED progressTracker.currentStep = ISSUED
val destroyBuilder = TransactionBuilder() val destroyBuilder = TransactionBuilder()
destroyBuilder.notary = notary; destroyBuilder.notary = notary
destroyBuilder.addInputState(issueResult.tx.outRefsOfType<NotaryTestState>().first()) destroyBuilder.addInputState(issueResult.tx.outRefsOfType<NotaryTestState>().first())
destroyBuilder.addCommand(NotaryTestCommand, myIdentity.owningKey) destroyBuilder.addCommand(NotaryTestCommand, myIdentity.owningKey)
val signedDestroyT = serviceHub.signInitialTransaction(destroyBuilder) val signedDestroyT = serviceHub.signInitialTransaction(destroyBuilder)
val result = subFlow(FinalityFlow(signedDestroyT)) val result = subFlow(FinalityFlow(signedDestroyT))
progressTracker.currentStep = DESTROYING progressTracker.currentStep = DESTROYING
progressTracker.currentStep = FINALIZED progressTracker.currentStep = FINALIZED
return "notarised: " + result.notary.toString() + "::" + result.tx.id return "notarised: ${result.notary}::${result.tx.id}"
} }
} }
@CordaSerializable @CordaSerializable
data class NotaryTestState(val id: String, val issuer: AbstractParty) : ContractState { data class NotaryTestState(val id: String, val issuer: AbstractParty) : ContractState {
override val participants: List<AbstractParty> override val participants: List<AbstractParty>
get() = listOf(issuer) get() = listOf(issuer)
} }
@CordaSerializable @CordaSerializable
object NotaryTestCommand : CommandData object NotaryTestCommand : CommandData
class NotaryTestContract : Contract { class NotaryTestContract : Contract {
override fun verify(tx: LedgerTransaction) { override fun verify(tx: LedgerTransaction) {
} }

View File

@ -19,8 +19,8 @@ dependencies {
// For AMQP serialisation. // For AMQP serialisation.
compile "org.apache.qpid:proton-j:$protonj_version" compile "org.apache.qpid:proton-j:$protonj_version"
// FastClasspathScanner: classpath scanning // ClassGraph: classpath scanning
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" compile "io.github.classgraph:classgraph:$class_graph_version"
// Pure-Java Snappy compression // Pure-Java Snappy compression
compile "org.iq80.snappy:snappy:$snappy_version" compile "org.iq80.snappy:snappy:$snappy_version"

View File

@ -2,11 +2,12 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.classgraph.ClassGraph
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM import net.corda.core.StubOutForDJVM
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.isAbstractClass
import net.corda.core.internal.objectOrNewInstance import net.corda.core.internal.objectOrNewInstance
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.* import net.corda.core.serialization.*
@ -16,7 +17,6 @@ import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.DefaultWhitelist import net.corda.serialization.internal.DefaultWhitelist
import net.corda.serialization.internal.MutableClassWhitelist import net.corda.serialization.internal.MutableClassWhitelist
import net.corda.serialization.internal.SerializationScheme import net.corda.serialization.internal.SerializationScheme
import java.lang.reflect.Modifier
import java.util.* import java.util.*
val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic
@ -72,10 +72,16 @@ abstract class AbstractAMQPSerializationScheme(
@StubOutForDJVM @StubOutForDJVM
private fun scanClasspathForSerializers(scanSpec: String): List<SerializationCustomSerializer<*, *>> = private fun scanClasspathForSerializers(scanSpec: String): List<SerializationCustomSerializer<*, *>> =
this::class.java.classLoader.let { cl -> this::class.java.classLoader.let { cl ->
FastClasspathScanner(scanSpec).addClassLoader(cl).scan() ClassGraph()
.getNamesOfClassesImplementing(SerializationCustomSerializer::class.java) .whitelistPackages(scanSpec)
.map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) } .addClassLoader(cl)
.filterNot { Modifier.isAbstract(it.modifiers) } .enableAllInfo()
.scan()
.use {
val serializerClass = SerializationCustomSerializer::class.java
it.getClassesImplementing(serializerClass.name).loadClasses(serializerClass)
}
.filterNot { it.isAbstractClass }
.map { it.kotlin.objectOrNewInstance() } .map { it.kotlin.objectOrNewInstance() }
} }

View File

@ -19,6 +19,7 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.PortAllocation.Incremental import net.corda.testing.driver.PortAllocation.Incremental
import net.corda.testing.driver.internal.internalServices import net.corda.testing.driver.internal.internalServices
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import rx.Observable import rx.Observable
@ -134,8 +135,8 @@ abstract class PortAllocation {
* in. If null the Driver-level value will be used. * in. If null the Driver-level value will be used.
* @property maximumHeapSize The maximum JVM heap size to use for the node. * @property maximumHeapSize The maximum JVM heap size to use for the node.
* @property logLevel Logging level threshold. * @property logLevel Logging level threshold.
* @property additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. * @property additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @property regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. * @property regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
*/ */
@Suppress("unused") @Suppress("unused")
data class NodeParameters( data class NodeParameters(
@ -146,7 +147,7 @@ data class NodeParameters(
val startInSameProcess: Boolean? = null, val startInSameProcess: Boolean? = null,
val maximumHeapSize: String = "512m", val maximumHeapSize: String = "512m",
val logLevel: String? = null, val logLevel: String? = null,
val additionalCordapps: Set<TestCorDapp> = emptySet(), val additionalCordapps: Collection<TestCordapp> = emptySet(),
val regenerateCordappsOnStart: Boolean = false val regenerateCordappsOnStart: Boolean = false
) { ) {
/** /**
@ -226,8 +227,8 @@ data class NodeParameters(
* @param startInSameProcess Determines if the node should be started inside the same process the Driver is running * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running
* in. If null the Driver-level value will be used. * in. If null the Driver-level value will be used.
* @param maximumHeapSize The maximum JVM heap size to use for the node. * @param maximumHeapSize The maximum JVM heap size to use for the node.
* @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. * @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
*/ */
constructor( constructor(
providedName: CordaX500Name?, providedName: CordaX500Name?,
@ -236,7 +237,7 @@ data class NodeParameters(
customOverrides: Map<String, Any?>, customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?, startInSameProcess: Boolean?,
maximumHeapSize: String, maximumHeapSize: String,
additionalCordapps: Set<TestCorDapp> = emptySet(), additionalCordapps: Set<TestCordapp> = emptySet(),
regenerateCordappsOnStart: Boolean = false regenerateCordappsOnStart: Boolean = false
) : this( ) : this(
providedName, providedName,
@ -291,7 +292,7 @@ data class NodeParameters(
fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess)
fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize)
fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel) fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel)
fun withAdditionalCordapps(additionalCordapps: Set<TestCorDapp>): NodeParameters = copy(additionalCordapps = additionalCordapps) fun withAdditionalCordapps(additionalCordapps: Set<TestCordapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
fun withDeleteExistingCordappsDirectory(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart) fun withDeleteExistingCordappsDirectory(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart)
} }
@ -379,7 +380,7 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
* @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however * @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however
* the data is not persisted between node restarts). Has no effect if node is configured * the data is not persisted between node restarts). Has no effect if node is configured
* in any way to use database other than H2. * in any way to use database other than H2.
* @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [DriverDSL]. * @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [DriverDSL].
*/ */
@Suppress("unused") @Suppress("unused")
data class DriverParameters( data class DriverParameters(
@ -398,7 +399,7 @@ data class DriverParameters(
val notaryCustomOverrides: Map<String, Any?> = emptyMap(), val notaryCustomOverrides: Map<String, Any?> = emptyMap(),
val initialiseSerialization: Boolean = true, val initialiseSerialization: Boolean = true,
val inMemoryDB: Boolean = true, val inMemoryDB: Boolean = true,
val cordappsForAllNodes: Set<TestCorDapp>? = null val cordappsForAllNodes: Collection<TestCordapp>? = null
) { ) {
constructor( constructor(
isDebug: Boolean = false, isDebug: Boolean = false,
@ -480,7 +481,7 @@ data class DriverParameters(
extraCordappPackagesToScan: List<String>, extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy, jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters, networkParameters: NetworkParameters,
cordappsForAllNodes: Set<TestCorDapp>? = null cordappsForAllNodes: Collection<TestCordapp>? = null
) : this( ) : this(
isDebug, isDebug,
driverDirectory, driverDirectory,
@ -549,7 +550,7 @@ data class DriverParameters(
networkParameters: NetworkParameters, networkParameters: NetworkParameters,
initialiseSerialization: Boolean, initialiseSerialization: Boolean,
inMemoryDB: Boolean, inMemoryDB: Boolean,
cordappsForAllNodes: Set<TestCorDapp>? = null cordappsForAllNodes: Set<TestCordapp>? = null
) : this( ) : this(
isDebug, isDebug,
driverDirectory, driverDirectory,
@ -584,7 +585,7 @@ data class DriverParameters(
fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters) fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters)
fun withNotaryCustomOverrides(notaryCustomOverrides: Map<String, Any?>): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides) fun withNotaryCustomOverrides(notaryCustomOverrides: Map<String, Any?>): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides)
fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB) fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB)
fun withCordappsForAllNodes(cordappsForAllNodes: Set<TestCorDapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes) fun withCordappsForAllNodes(cordappsForAllNodes: Set<TestCordapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes)
fun copy( fun copy(
isDebug: Boolean, isDebug: Boolean,
@ -660,7 +661,7 @@ data class DriverParameters(
extraCordappPackagesToScan: List<String>, extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy, jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters, networkParameters: NetworkParameters,
cordappsForAllNodes: Set<TestCorDapp>? cordappsForAllNodes: Set<TestCordapp>?
) = this.copy( ) = this.copy(
isDebug = isDebug, isDebug = isDebug,
driverDirectory = driverDirectory, driverDirectory = driverDirectory,
@ -693,7 +694,7 @@ data class DriverParameters(
jmxPolicy: JmxPolicy, jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters, networkParameters: NetworkParameters,
initialiseSerialization: Boolean, initialiseSerialization: Boolean,
cordappsForAllNodes: Set<TestCorDapp>? cordappsForAllNodes: Set<TestCordapp>?
) = this.copy( ) = this.copy(
isDebug = isDebug, isDebug = isDebug,
driverDirectory = driverDirectory, driverDirectory = driverDirectory,

View File

@ -6,6 +6,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.map
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import java.nio.file.Path import java.nio.file.Path
@ -95,8 +96,8 @@ interface DriverDSL {
* @param maximumHeapSize The maximum JVM heap size to use for the node as a [String]. By default a number is interpreted * @param maximumHeapSize The maximum JVM heap size to use for the node as a [String]. By default a number is interpreted
* as being in bytes. Append the letter 'k' or 'K' to the value to indicate Kilobytes, 'm' or 'M' to indicate * as being in bytes. Append the letter 'k' or 'K' to the value to indicate Kilobytes, 'm' or 'M' to indicate
* megabytes, and 'g' or 'G' to indicate gigabytes. The default value is "512m" = 512 megabytes. * megabytes, and 'g' or 'G' to indicate gigabytes. The default value is "512m" = 512 megabytes.
* @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. * @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
* @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and
* it sees all previously started nodes, including the notaries. * it sees all previously started nodes, including the notaries.
*/ */
@ -108,7 +109,7 @@ interface DriverDSL {
customOverrides: Map<String, Any?> = defaultParameters.customOverrides, customOverrides: Map<String, Any?> = defaultParameters.customOverrides,
startInSameProcess: Boolean? = defaultParameters.startInSameProcess, startInSameProcess: Boolean? = defaultParameters.startInSameProcess,
maximumHeapSize: String = defaultParameters.maximumHeapSize, maximumHeapSize: String = defaultParameters.maximumHeapSize,
additionalCordapps: Set<TestCorDapp> = defaultParameters.additionalCordapps, additionalCordapps: Collection<TestCordapp> = defaultParameters.additionalCordapps,
regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart
): CordaFuture<NodeHandle> ): CordaFuture<NodeHandle>

View File

@ -1,85 +0,0 @@
package net.corda.testing.driver
import net.corda.core.DoNotImplement
import net.corda.testing.node.internal.MutableTestCorDapp
import java.net.URL
import java.nio.file.Path
/**
* Represents information about a CorDapp. Used to generate CorDapp JARs in tests.
*/
@DoNotImplement
interface TestCorDapp {
val name: String
val title: String
val version: String
val vendor: String
val classes: Set<Class<*>>
val resources: Set<URL>
fun packageAsJarInDirectory(parentDirectory: Path): Path
fun packageAsJarWithPath(jarFilePath: Path)
/**
* Responsible of creating [TestCorDapp]s.
*/
class Factory {
companion object {
/**
* Returns a builder-style [TestCorDapp] to easily generate different [TestCorDapp]s that have something in common.
*/
@JvmStatic
fun create(name: String, version: String, vendor: String = "R3", title: String = name, classes: Set<Class<*>> = emptySet(), willResourceBeAddedBeToCorDapp: (fullyQualifiedName: String, url: URL) -> Boolean = MutableTestCorDapp.Companion::filterTestCorDappClass): TestCorDapp.Mutable {
return MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedBeToCorDapp)
}
}
}
@DoNotImplement
interface Mutable : TestCorDapp {
fun withName(name: String): TestCorDapp.Mutable
fun withTitle(title: String): TestCorDapp.Mutable
fun withVersion(version: String): TestCorDapp.Mutable
fun withVendor(vendor: String): TestCorDapp.Mutable
fun withClasses(classes: Set<Class<*>>): TestCorDapp.Mutable
fun plusPackages(pckgs: Set<String>): TestCorDapp.Mutable
fun minusPackages(pckgs: Set<String>): TestCorDapp.Mutable
fun plusPackage(pckg: String): TestCorDapp.Mutable = plusPackages(setOf(pckg))
fun minusPackage(pckg: String): TestCorDapp.Mutable = minusPackages(setOf(pckg))
fun plusPackage(pckg: Package): TestCorDapp.Mutable = plusPackages(pckg.name)
fun minusPackage(pckg: Package): TestCorDapp.Mutable = minusPackages(pckg.name)
operator fun plus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes + clazz)
operator fun minus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes - clazz)
fun plusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = plusPackages(setOf(pckg, *pckgs))
fun plusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet())
fun minusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs))
fun minusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet())
fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable
fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable
}
}

View File

@ -16,7 +16,6 @@ import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import rx.Observable import rx.Observable
import java.math.BigInteger import java.math.BigInteger
@ -34,36 +33,29 @@ import java.util.concurrent.Future
* @property entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, * @property entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys. * but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object. * @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @property additionalCordapps [TestCorDapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level. * @property additionalCordapps [TestCordapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level.
*/ */
@Suppress("unused") @Suppress("unused")
data class MockNodeParameters constructor( data class MockNodeParameters(
val forcedID: Int? = null, val forcedID: Int? = null,
val legalName: CordaX500Name? = null, val legalName: CordaX500Name? = null,
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {}, val configOverrides: (NodeConfiguration) -> Any? = {},
val additionalCordapps: Set<TestCorDapp>) { val additionalCordapps: Collection<TestCordapp> = emptyList()) {
@JvmOverloads constructor(forcedID: Int? = null,
constructor(
forcedID: Int? = null,
legalName: CordaX500Name? = null, legalName: CordaX500Name? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {}, configOverrides: (NodeConfiguration) -> Any? = {}
extraCordappPackages: List<String> = emptyList() ) : this(forcedID, legalName, entropyRoot, configOverrides, emptyList())
) : this(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = cordappsForPackages(extraCordappPackages))
fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID) fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID)
fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName) fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName)
fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot) fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot)
fun withConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides) fun withConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides)
fun withExtraCordappPackages(extraCordappPackages: List<String>): MockNodeParameters = copy(forcedID = forcedID, legalName = legalName, entropyRoot = entropyRoot, configOverrides = configOverrides, extraCordappPackages = extraCordappPackages) fun withAdditionalCordapps(additionalCordapps: Collection<TestCordapp>): MockNodeParameters = copy(additionalCordapps = additionalCordapps)
fun withAdditionalCordapps(additionalCordapps: Set<TestCorDapp>): MockNodeParameters = copy(additionalCordapps = additionalCordapps)
fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters { fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters {
return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = emptySet()) return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides)
}
fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?, extraCordappPackages: List<String> = emptyList()): MockNodeParameters {
return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages)
} }
} }
@ -293,7 +285,7 @@ inline fun <reified F : FlowLogic<*>> StartedMockNode.registerResponderFlow(
* @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient. * @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient.
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
* empty as notaries are defined by [notarySpecs]. * empty as notaries are defined by [notarySpecs].
* @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [MockNetwork]. * @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [MockNetwork].
*/ */
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
open class MockNetwork( open class MockNetwork(
@ -304,7 +296,7 @@ open class MockNetwork(
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs, val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val networkParameters: NetworkParameters = defaultParameters.networkParameters, val networkParameters: NetworkParameters = defaultParameters.networkParameters,
val cordappsForAllNodes: Set<TestCorDapp> = cordappsForPackages(cordappPackages)) { val cordappsForAllNodes: Collection<TestCordapp> = cordappsForPackages(cordappPackages)) {
@JvmOverloads @JvmOverloads
constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters)
@ -345,7 +337,9 @@ open class MockNetwork(
fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode = StartedMockNode.create(internalMockNetwork.createPartyNode(legalName)) fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode = StartedMockNode.create(internalMockNetwork.createPartyNode(legalName))
/** Create a started node with the given parameters. **/ /** Create a started node with the given parameters. **/
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode = StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode {
return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
}
/** /**
* Create a started node with the given parameters. * Create a started node with the given parameters.
@ -375,13 +369,13 @@ open class MockNetwork(
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys. * but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork].
*/ */
fun createNode(legalName: CordaX500Name? = null, fun createNode(legalName: CordaX500Name? = null,
forcedID: Int? = null, forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {}, configOverrides: (NodeConfiguration) -> Any? = {},
additionalCordapps: Set<TestCorDapp>): StartedMockNode { additionalCordapps: Collection<TestCordapp>): StartedMockNode {
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
} }
@ -417,13 +411,13 @@ open class MockNetwork(
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys. * but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork].
*/ */
fun createUnstartedNode(legalName: CordaX500Name? = null, fun createUnstartedNode(legalName: CordaX500Name? = null,
forcedID: Int? = null, forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {}, configOverrides: (NodeConfiguration) -> Any? = {},
additionalCordapps: Set<TestCorDapp>): UnstartedMockNode { additionalCordapps: Collection<TestCordapp>): UnstartedMockNode {
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
} }

View File

@ -66,8 +66,7 @@ open class MockServices private constructor(
companion object { companion object {
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
val cordappPaths = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) }
val cordappPaths = TestCordappDirectories.forPackages(packages)
return JarScanningCordappLoader.fromDirectories(cordappPaths, versionInfo) return JarScanningCordappLoader.fromDirectories(cordappPaths, versionInfo)
} }

View File

@ -0,0 +1,69 @@
package net.corda.testing.node
import net.corda.core.DoNotImplement
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.testing.node.internal.TestCordappImpl
import net.corda.testing.node.internal.simplifyScanPackages
/**
* Represents information about a CorDapp. Used to generate CorDapp JARs in tests.
*/
@DoNotImplement
interface TestCordapp {
/** Returns the name, defaults to "test-cordapp" if not specified. */
val name: String
/** Returns the title, defaults to "test-title" if not specified. */
val title: String
/** Returns the version string, defaults to "1.0" if not specified. */
val version: String
/** Returns the vendor string, defaults to "Corda" if not specified. */
val vendor: String
/** Returns the target platform version, defaults to the current platform version if not specified. */
val targetVersion: Int
/** Returns the set of package names scanned for this test CorDapp. */
val packages: Set<String>
/** Return a copy of this [TestCordapp] but with the specified name. */
fun withName(name: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified title. */
fun withTitle(title: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified version string. */
fun withVersion(version: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified vendor string. */
fun withVendor(vendor: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified target platform version. */
fun withTargetVersion(targetVersion: Int): TestCordapp
class Factory {
companion object {
@JvmStatic
fun fromPackages(vararg packageNames: String): TestCordapp = fromPackages(packageNames.asList())
/**
* Create a [TestCordapp] object by scanning the given packages. The meta data on the CorDapp will be the
* default values, which can be specified with the wither methods.
*/
@JvmStatic
fun fromPackages(packageNames: Collection<String>): TestCordapp {
return TestCordappImpl(
name = "test-cordapp",
version = "1.0",
vendor = "Corda",
title = "test-title",
targetVersion = PLATFORM_VERSION,
packages = simplifyScanPackages(packageNames),
classes = emptySet()
)
}
}
}
}

View File

@ -25,7 +25,6 @@ import net.corda.node.services.Permissions
import net.corda.node.services.config.* import net.corda.node.services.config.*
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
@ -35,6 +34,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.testing.node.TestCordapp
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME
@ -88,7 +88,7 @@ class DriverDSLImpl(
val networkParameters: NetworkParameters, val networkParameters: NetworkParameters,
val notaryCustomOverrides: Map<String, Any?>, val notaryCustomOverrides: Map<String, Any?>,
val inMemoryDB: Boolean, val inMemoryDB: Boolean,
val cordappsForAllNodes: Set<TestCorDapp> val cordappsForAllNodes: Collection<TestCordapp>
) : InternalDriverDSL { ) : InternalDriverDSL {
private var _executorService: ScheduledExecutorService? = null private var _executorService: ScheduledExecutorService? = null
@ -193,7 +193,26 @@ class DriverDSLImpl(
} }
} }
override fun startNode(defaultParameters: NodeParameters, providedName: CordaX500Name?, rpcUsers: List<User>, verifierType: VerifierType, customOverrides: Map<String, Any?>, startInSameProcess: Boolean?, maximumHeapSize: String) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, defaultParameters.additionalCordapps, defaultParameters.regenerateCordappsOnStart, null) override fun startNode(defaultParameters: NodeParameters,
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String): CordaFuture<NodeHandle> {
return startNode(
defaultParameters,
providedName,
rpcUsers,
verifierType,
customOverrides,
startInSameProcess,
maximumHeapSize,
defaultParameters.additionalCordapps,
defaultParameters.regenerateCordappsOnStart,
null
)
}
override fun startNode( override fun startNode(
defaultParameters: NodeParameters, defaultParameters: NodeParameters,
@ -203,7 +222,7 @@ class DriverDSLImpl(
customOverrides: Map<String, Any?>, customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?, startInSameProcess: Boolean?,
maximumHeapSize: String, maximumHeapSize: String,
additionalCordapps: Set<TestCorDapp>, additionalCordapps: Collection<TestCordapp>,
regenerateCordappsOnStart: Boolean regenerateCordappsOnStart: Boolean
) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, additionalCordapps, regenerateCordappsOnStart, null) ) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, additionalCordapps, regenerateCordappsOnStart, null)
@ -247,7 +266,7 @@ class DriverDSLImpl(
startInSameProcess: Boolean? = null, startInSameProcess: Boolean? = null,
maximumHeapSize: String = "512m", maximumHeapSize: String = "512m",
p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(),
additionalCordapps: Set<TestCorDapp> = emptySet(), additionalCordapps: Collection<TestCordapp> = emptySet(),
regenerateCordappsOnStart: Boolean = false, regenerateCordappsOnStart: Boolean = false,
bytemanPort: Int? = null): CordaFuture<NodeHandle> { bytemanPort: Int? = null): CordaFuture<NodeHandle> {
val rpcAddress = portAllocation.nextHostAndPort() val rpcAddress = portAllocation.nextHostAndPort()
@ -561,16 +580,12 @@ class DriverDSLImpl(
} }
} }
private val sharedCordappsDirectories: Iterable<Path> by lazy {
TestCordappDirectories.cached(cordappsForAllNodes)
}
private fun startNodeInternal(specifiedConfig: NodeConfig, private fun startNodeInternal(specifiedConfig: NodeConfig,
webAddress: NetworkHostAndPort, webAddress: NetworkHostAndPort,
startInProcess: Boolean?, startInProcess: Boolean?,
maximumHeapSize: String, maximumHeapSize: String,
localNetworkMap: LocalNetworkMap?, localNetworkMap: LocalNetworkMap?,
additionalCordapps: Set<TestCorDapp>, additionalCordapps: Collection<TestCordapp>,
regenerateCordappsOnStart: Boolean = false, regenerateCordappsOnStart: Boolean = false,
bytemanPort: Int? = null): CordaFuture<NodeHandle> { bytemanPort: Int? = null): CordaFuture<NodeHandle> {
val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName) val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName)
@ -585,11 +600,17 @@ class DriverDSLImpl(
val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") }
val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) emptyList<String>() else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) {
emptyList()
} else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) {
specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey)
} else {
emptyList()
}
val cordappDirectories = existingCorDappDirectoriesOption + sharedCordappsDirectories.map { it.toString() } + TestCordappDirectories.cached(additionalCordapps, regenerateCordappsOnStart).map { it.toString() } val cordappDirectories = existingCorDappDirectoriesOption + (cordappsForAllNodes + additionalCordapps).map { TestCordappDirectories.getJarDirectory(it).toString() }
val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories))) val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet())))
if (startInProcess ?: startNodesInProcess) { if (startInProcess ?: startNodesInProcess) {
val nodeAndThreadFuture = startInProcessNode(executorService, config) val nodeAndThreadFuture = startInProcessNode(executorService, config)
@ -714,8 +735,13 @@ class DriverDSLImpl(
private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)] private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)]
fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Iterable<String> = emptySet()): Set<TestCorDapp> = cordappsForPackages(getCallerPackage() + packagesToScan) fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Collection<String> = emptySet()): List<TestCordapp> {
fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): Set<TestCorDapp> = cordappsInCurrentAndAdditionalPackages(otherPackages.toList() + firstPackage) return cordappsForPackages(getCallerPackage() + packagesToScan)
}
fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): List<TestCordapp> {
return cordappsInCurrentAndAdditionalPackages(otherPackages.asList() + firstPackage)
}
private fun startInProcessNode( private fun startInProcessNode(
executorService: ScheduledExecutorService, executorService: ScheduledExecutorService,
@ -1139,7 +1165,7 @@ fun <A> internalDriver(
compatibilityZone: CompatibilityZoneParams? = null, compatibilityZone: CompatibilityZoneParams? = null,
notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides, notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides,
inMemoryDB: Boolean = DriverParameters().inMemoryDB, inMemoryDB: Boolean = DriverParameters().inMemoryDB,
cordappsForAllNodes: Set<TestCorDapp> = DriverParameters().cordappsForAllNodes(), cordappsForAllNodes: Collection<TestCordapp> = DriverParameters().cordappsForAllNodes(),
dsl: DriverDSLImpl.() -> A dsl: DriverDSLImpl.() -> A
): A { ): A {
return genericDriver( return genericDriver(
@ -1179,7 +1205,7 @@ private fun Config.toNodeOnly(): Config {
return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this
} }
internal fun DriverParameters.cordappsForAllNodes(): Set<TestCorDapp> = cordappsForAllNodes internal fun DriverParameters.cordappsForAllNodes(): Collection<TestCordapp> = cordappsForAllNodes
?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan)
fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture<NodeHandle> { fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture<NodeHandle> {

View File

@ -48,8 +48,8 @@ import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.node.TestCordapp
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.setGlobalSerialization import net.corda.testing.internal.setGlobalSerialization
import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.stubs.CertificateStoreStubs
@ -85,7 +85,7 @@ data class InternalMockNodeParameters(
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {}, val configOverrides: (NodeConfiguration) -> Any? = {},
val version: VersionInfo = MOCK_VERSION_INFO, val version: VersionInfo = MOCK_VERSION_INFO,
val additionalCordapps: Set<TestCorDapp>? = null) { val additionalCordapps: Collection<TestCordapp>? = null) {
constructor(mockNodeParameters: MockNodeParameters) : this( constructor(mockNodeParameters: MockNodeParameters) : this(
mockNodeParameters.forcedID, mockNodeParameters.forcedID,
mockNodeParameters.legalName, mockNodeParameters.legalName,
@ -144,7 +144,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
val networkParameters: NetworkParameters = testNetworkParameters(), val networkParameters: NetworkParameters = testNetworkParameters(),
val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) }, val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) },
val cordappsForAllNodes: Set<TestCorDapp> = emptySet(), val cordappsForAllNodes: Collection<TestCordapp> = emptySet(),
val autoVisibleNodes: Boolean = true) : AutoCloseable { val autoVisibleNodes: Boolean = true) : AutoCloseable {
init { init {
// Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS. // Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS.
@ -170,10 +170,6 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
} }
private val sharedUserCount = AtomicInteger(0) private val sharedUserCount = AtomicInteger(0)
private val sharedCorDappsDirectories: Iterable<Path> by lazy {
TestCordappDirectories.cached(cordappsForAllNodes)
}
/** A read only view of the current set of nodes. */ /** A read only view of the current set of nodes. */
val nodes: List<MockNode> get() = _nodes val nodes: List<MockNode> get() = _nodes
@ -450,8 +446,8 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
parameters.configOverrides(it) parameters.configOverrides(it)
} }
val cordapps: Set<TestCorDapp> = parameters.additionalCordapps ?: emptySet() val cordapps = (parameters.additionalCordapps ?: emptySet()) + cordappsForAllNodes
val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps) val cordappDirectories = cordapps.map { TestCordappDirectories.getJarDirectory(it) }.distinct()
doReturn(cordappDirectories).whenever(config).cordappDirectories doReturn(cordappDirectories).whenever(config).cordappDirectories
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version)) val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version))

View File

@ -1,73 +0,0 @@
package net.corda.testing.node.internal
import net.corda.core.internal.div
import net.corda.testing.driver.TestCorDapp
import java.io.File
import java.net.URL
import java.nio.file.Path
internal class MutableTestCorDapp private constructor(override val name: String, override val version: String, override val vendor: String, override val title: String, private val willResourceBeAddedToCorDapp: (String, URL) -> Boolean, private val jarEntries: Set<JarEntryInfo>) : TestCorDapp.Mutable {
constructor(name: String, version: String, vendor: String, title: String, classes: Set<Class<*>>, willResourceBeAddedToCorDapp: (String, URL) -> Boolean) : this(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntriesFromClasses(classes))
companion object {
private const val jarExtension = ".jar"
private const val whitespace = " "
private const val whitespaceReplacement = "_"
private val productionPathSegments = setOf<(String) -> String>({ "out${File.separator}production${File.separator}classes" }, { fullyQualifiedName -> "main${File.separator}${fullyQualifiedName.packageToJarPath()}" })
private val excludedCordaPackages = setOf("net.corda.core", "net.corda.node")
fun filterTestCorDappClass(fullyQualifiedName: String, url: URL): Boolean {
return isTestResource(fullyQualifiedName, url) || !isInExcludedCordaPackage(fullyQualifiedName)
}
private fun isTestResource(fullyQualifiedName: String, url: URL): Boolean {
return productionPathSegments.map { it.invoke(fullyQualifiedName) }.none { url.toString().contains(it) }
}
private fun isInExcludedCordaPackage(packageName: String): Boolean {
return excludedCordaPackages.any { packageName.startsWith(it) }
}
private fun jarEntriesFromClasses(classes: Set<Class<*>>): Set<JarEntryInfo> {
val illegal = classes.filter { it.protectionDomain?.codeSource?.location == null }
if (illegal.isNotEmpty()) {
throw IllegalArgumentException("Some classes do not have a location, typically because they are part of Java or Kotlin. Offending types were: ${illegal.joinToString(", ", "[", "]") { it.simpleName }}")
}
return classes.map(Class<*>::jarEntryInfo).toSet()
}
}
override val classes: Set<Class<*>> = jarEntries.filterIsInstance(JarEntryInfo.ClassJarEntryInfo::class.java).map(JarEntryInfo.ClassJarEntryInfo::clazz).toSet()
override val resources: Set<URL> = jarEntries.map(JarEntryInfo::url).toSet()
override fun withName(name: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withTitle(title: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withVersion(version: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withVendor(vendor: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withClasses(classes: Set<Class<*>>) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun plusPackages(pckgs: Set<String>) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all + packageClasses })
override fun minusPackages(pckgs: Set<String>) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all - packageClasses })
override fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries + JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url))
override fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries - JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url))
override fun packageAsJarWithPath(jarFilePath: Path) = jarEntries.packageToCorDapp(jarFilePath, name, version, vendor, title, willResourceBeAddedToCorDapp)
override fun packageAsJarInDirectory(parentDirectory: Path): Path = (parentDirectory / defaultJarName()).also { packageAsJarWithPath(it) }
private fun defaultJarName(): String = "${name}_$version$jarExtension".replace(whitespace, whitespaceReplacement)
}

View File

@ -9,7 +9,6 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.internal.NodeWithInfo import net.corda.node.internal.NodeWithInfo
import net.corda.node.internal.EnterpriseNode import net.corda.node.internal.EnterpriseNode
@ -113,9 +112,9 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList()
val cordappDirectories = existingCorDappDirectoriesOption + TestCordappDirectories.cached(cordapps).map { it.toString() } val cordappDirectories = existingCorDappDirectoriesOption + cordapps.map { TestCordappDirectories.getJarDirectory(it).toString() }
val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories)) val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))
val parsedConfig = specificConfig.parseAsNodeConfiguration().also { nodeConfiguration -> val parsedConfig = specificConfig.parseAsNodeConfiguration().also { nodeConfiguration ->
val errors = nodeConfiguration.validate() val errors = nodeConfiguration.validate()

View File

@ -24,11 +24,11 @@ import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.internal.ArtemisTcpTransport import net.corda.nodeapi.internal.ArtemisTcpTransport
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.testing.node.TestCordapp
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.JmxPolicy
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
@ -118,7 +118,7 @@ fun <A> rpcDriver(
networkParameters: NetworkParameters = testNetworkParameters(), networkParameters: NetworkParameters = testNetworkParameters(),
notaryCustomOverrides: Map<String, Any?> = emptyMap(), notaryCustomOverrides: Map<String, Any?> = emptyMap(),
inMemoryDB: Boolean = true, inMemoryDB: Boolean = true,
cordappsForAllNodes: Set<TestCorDapp> = cordappsInCurrentAndAdditionalPackages(), cordappsForAllNodes: Collection<TestCordapp> = cordappsInCurrentAndAdditionalPackages(),
dsl: RPCDriverDSL.() -> A dsl: RPCDriverDSL.() -> A
): A { ): A {
return genericDriver( return genericDriver(

View File

@ -1,70 +1,46 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import net.corda.core.crypto.sha256
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
import net.corda.core.internal.deleteRecursively import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.exists import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.testing.driver.TestCorDapp import net.corda.testing.node.TestCordapp
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
object TestCordappDirectories { object TestCordappDirectories {
private val logger = loggerFor<TestCordappDirectories>() private val logger = loggerFor<TestCordappDirectories>()
private const val whitespace = " " private val whitespace = "\\s".toRegex()
private const val whitespaceReplacement = "_"
private val cordappsCache: ConcurrentMap<List<String>, Path> = ConcurrentHashMap<List<String>, Path>() private val testCordappsCache = ConcurrentHashMap<TestCordappImpl, Path>()
fun cached(cordapps: Iterable<TestCorDapp>, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable<Path> { fun getJarDirectory(cordapp: TestCordapp, cordappsDirectory: Path = defaultCordappsDirectory): Path {
cordapp as TestCordappImpl
cordappsDirectory.toFile().deleteOnExit() return testCordappsCache.computeIfAbsent(cordapp) {
return cordapps.map { cached(it, replaceExistingOnes, cordappsDirectory) } val cordappDir = (cordappsDirectory / UUID.randomUUID().toString()).createDirectories()
} val uniqueScanString = if (cordapp.packages.size == 1 && cordapp.classes.isEmpty()) {
cordapp.packages.first()
internal fun cached(cordapp: TestCorDapp, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Path {
cordappsDirectory.toFile().deleteOnExit()
val cacheKey = cordapp.resources.map { it.toExternalForm() }.sorted()
return if (replaceExistingOnes) {
cordappsCache.remove(cacheKey)
cordappsCache.getOrPut(cacheKey) {
val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath()
cordappDirectory.createDirectories()
cordapp.packageAsJarInDirectory(cordappDirectory)
cordappDirectory
}
} else { } else {
cordappsCache.getOrPut(cacheKey) { "${cordapp.packages}${cordapp.classes.joinToString { it.name }}".toByteArray().sha256().toString()
val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath()
cordappDirectory.createDirectories()
cordapp.packageAsJarInDirectory(cordappDirectory)
cordappDirectory
} }
val jarFileName = cordapp.run { "${name}_${vendor}_${title}_${version}_${targetVersion}_$uniqueScanString.jar".replace(whitespace, "-") }
val jarFile = cordappDir / jarFileName
cordapp.packageAsJar(jarFile)
logger.debug { "$cordapp packaged into $jarFile" }
cordappDir
} }
} }
internal fun forPackages(packages: Iterable<String>, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable<Path> {
cordappsDirectory.toFile().deleteOnExit()
val cordapps = simplifyScanPackages(packages).distinct().fold(emptySet<TestCorDapp>()) { all, packageName -> all + testCorDapp(packageName) }
return cached(cordapps, replaceExistingOnes, cordappsDirectory)
}
private val defaultCordappsDirectory: Path by lazy { private val defaultCordappsDirectory: Path by lazy {
val cordappsDirectory = (Paths.get("build") / "tmp" / getTimestampAsDirectoryName() / "generated-test-cordapps").toAbsolutePath() val cordappsDirectory = (Paths.get("build") / "tmp" / getTimestampAsDirectoryName() / "generated-test-cordapps").toAbsolutePath()
logger.info("Initialising generated test CorDapps directory in $cordappsDirectory") logger.info("Initialising generated test CorDapps directory in $cordappsDirectory")
if (cordappsDirectory.exists()) { cordappsDirectory.toFile().deleteOnExit()
cordappsDirectory.deleteRecursively() cordappsDirectory.deleteRecursively()
}
cordappsDirectory.createDirectories() cordappsDirectory.createDirectories()
cordappsDirectory
} }
} }

View File

@ -0,0 +1,26 @@
package net.corda.testing.node.internal
import net.corda.testing.node.TestCordapp
data class TestCordappImpl(override val name: String,
override val version: String,
override val vendor: String,
override val title: String,
override val targetVersion: Int,
override val packages: Set<String>,
val classes: Set<Class<*>>) : TestCordapp {
override fun withName(name: String): TestCordappImpl = copy(name = name)
override fun withVersion(version: String): TestCordappImpl = copy(version = version)
override fun withVendor(vendor: String): TestCordappImpl = copy(vendor = vendor)
override fun withTitle(title: String): TestCordappImpl = copy(title = title)
override fun withTargetVersion(targetVersion: Int): TestCordappImpl = copy(targetVersion = targetVersion)
fun withClasses(vararg classes: Class<*>): TestCordappImpl {
return copy(classes = classes.filter { clazz -> packages.none { clazz.name.startsWith("$it.") } }.toSet())
}
}

View File

@ -1,97 +1,34 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.classgraph.ClassGraph
import net.corda.core.internal.createDirectories
import net.corda.core.internal.deleteIfExists
import net.corda.core.internal.outputStream import net.corda.core.internal.outputStream
import net.corda.node.internal.cordapp.createTestManifest import net.corda.node.internal.cordapp.createTestManifest
import net.corda.testing.driver.TestCorDapp import net.corda.testing.node.TestCordapp
import org.apache.commons.io.IOUtils
import java.io.OutputStream
import java.net.URI
import java.net.URL
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.attribute.FileTime import java.nio.file.attribute.FileTime
import java.time.Instant import java.time.Instant
import java.util.*
import java.util.jar.JarOutputStream import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** @JvmField
* Packages some [JarEntryInfo] into a CorDapp JAR with specified [path]. val FINANCE_CORDAPP: TestCordappImpl = cordappForPackages("net.corda.finance")
* @param path The path of the JAR.
* @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR.
*/
internal fun Iterable<JarEntryInfo>.packageToCorDapp(path: Path, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }) {
var hasContent = false /** Creates a [TestCordappImpl] for each package. */
try { fun cordappsForPackages(vararg packageNames: String): List<TestCordappImpl> = cordappsForPackages(packageNames.asList())
hasContent = packageToCorDapp(path.outputStream(), name, version, vendor, title, willResourceBeAddedBeToCorDapp)
} finally { fun cordappsForPackages(packageNames: Iterable<String>): List<TestCordappImpl> {
if (!hasContent) { return simplifyScanPackages(packageNames).map { cordappForPackages(it) }
path.deleteIfExists()
}
}
} }
/** /** Creates a single [TestCordappImpl] containing all the given packges. */
* Packages some [JarEntryInfo] into a CorDapp JAR using specified [outputStream]. fun cordappForPackages(vararg packageNames: String): TestCordappImpl {
* @param outputStream The [OutputStream] for the JAR. return TestCordapp.Factory.fromPackages(*packageNames) as TestCordappImpl
* @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR.
*/
internal fun Iterable<JarEntryInfo>.packageToCorDapp(outputStream: OutputStream, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }): Boolean {
val manifest = createTestManifest(name, title, version, vendor)
return JarOutputStream(outputStream, manifest).use { jos -> zip(jos, willResourceBeAddedBeToCorDapp) }
} }
/** fun cordappForClasses(vararg classes: Class<*>): TestCordappImpl = cordappForPackages().withClasses(*classes)
* Transforms a [Class] into a [JarEntryInfo].
*/
internal fun Class<*>.jarEntryInfo(): JarEntryInfo {
return JarEntryInfo.ClassJarEntryInfo(this)
}
/**
* Packages some [TestCorDapp]s under a root [directory], each with it's own JAR.
* @param directory The parent directory in which CorDapp JAR will be created.
*/
fun Iterable<TestCorDapp>.packageInDirectory(directory: Path) {
directory.createDirectories()
forEach { cordapp -> cordapp.packageAsJarInDirectory(directory) }
}
/**
* Returns all classes within the [targetPackage].
*/
fun allClassesForPackage(targetPackage: String): Set<Class<*>> {
val scanResult = FastClasspathScanner(targetPackage).strictWhitelist().scan()
return scanResult.namesOfAllClasses.filter { className -> className.startsWith(targetPackage) }.map(scanResult::classNameToClassRef).toSet()
}
/**
* Maps each package to a [TestCorDapp] with resources found in that package.
*/
fun cordappsForPackages(packages: Iterable<String>): Set<TestCorDapp> {
return simplifyScanPackages(packages).toSet().fold(emptySet()) { all, packageName -> all + testCorDapp(packageName) }
}
/**
* Maps each package to a [TestCorDapp] with resources found in that package.
*/
fun cordappsForPackages(firstPackage: String, vararg otherPackages: String): Set<TestCorDapp> {
return cordappsForPackages(setOf(*otherPackages) + firstPackage)
}
fun getCallerClass(directCallerClass: KClass<*>): Class<*>? { fun getCallerClass(directCallerClass: KClass<*>): Class<*>? {
val stackTrace = Throwable().stackTrace val stackTrace = Throwable().stackTrace
val index = stackTrace.indexOfLast { it.className == directCallerClass.java.name } val index = stackTrace.indexOfLast { it.className == directCallerClass.java.name }
if (index == -1) return null if (index == -1) return null
@ -102,112 +39,42 @@ fun getCallerClass(directCallerClass: KClass<*>): Class<*>? {
} }
} }
fun getCallerPackage(directCallerClass: KClass<*>): String? { fun getCallerPackage(directCallerClass: KClass<*>): String? = getCallerClass(directCallerClass)?.`package`?.name
return getCallerClass(directCallerClass)?.`package`?.name
}
/**
* Returns a [TestCorDapp] containing resources found in [packageName].
*/
internal fun testCorDapp(packageName: String): TestCorDapp {
val uuid = UUID.randomUUID()
val name = "$packageName-$uuid"
val version = "$uuid"
return TestCorDapp.Factory.create(name, version).plusPackage(packageName)
}
/** /**
* Squashes child packages if the parent is present. Example: ["com.foo", "com.foo.bar"] into just ["com.foo"]. * Squashes child packages if the parent is present. Example: ["com.foo", "com.foo.bar"] into just ["com.foo"].
*/ */
fun simplifyScanPackages(scanPackages: Iterable<String>): List<String> { fun simplifyScanPackages(scanPackages: Iterable<String>): Set<String> {
return scanPackages.sorted().fold(emptySet()) { soFar, packageName ->
return scanPackages.sorted().fold(emptyList()) { listSoFar, packageName ->
when { when {
listSoFar.isEmpty() -> listOf(packageName) soFar.isEmpty() -> setOf(packageName)
packageName.startsWith(listSoFar.last()) -> listSoFar packageName.startsWith("${soFar.last()}.") -> soFar
else -> listSoFar + packageName else -> soFar + packageName
} }
} }
} }
/** fun TestCordappImpl.packageAsJar(file: Path) {
* Transforms a class or package name into a path segment. // Don't mention "classes" in the error message as that feature is only available internally
*/ require(packages.isNotEmpty() || classes.isNotEmpty()) { "At least one package must be specified" }
internal fun String.packageToJarPath() = replace(".", "/")
private fun Iterable<JarEntryInfo>.zip(outputStream: ZipOutputStream, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean): Boolean { val scanResult = ClassGraph()
.whitelistPackages(*packages.toTypedArray())
.whitelistClasses(*classes.map { it.name }.toTypedArray())
.scan()
val entries = filter { (fullyQualifiedName, url) -> willResourceBeAddedBeToCorDapp(fullyQualifiedName, url) } scanResult.use {
if (entries.isNotEmpty()) { val manifest = createTestManifest(name, title, version, vendor, targetVersion)
zip(outputStream, entries) JarOutputStream(file.outputStream(), manifest).use { jos ->
} val time = FileTime.from(Instant.now())
return entries.isNotEmpty() // The same resource may be found in different locations (this will happen when running from gradle) so just
} // pick the first one found.
scanResult.allResources.asMap().forEach { path, resourceList ->
private fun zip(outputStream: ZipOutputStream, allInfo: Iterable<JarEntryInfo>) { val entry = ZipEntry(path).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
jos.putNextEntry(entry)
val time = FileTime.from(Instant.EPOCH) resourceList[0].open().use { it.copyTo(jos) }
val classLoader = Thread.currentThread().contextClassLoader jos.closeEntry()
allInfo.distinctBy { it.url }.sortedBy { it.url.toExternalForm() }.forEach { info ->
try {
val entry = ZipEntry(info.entryName).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
outputStream.putNextEntry(entry)
classLoader.getResourceAsStream(info.entryName).use {
IOUtils.copy(it, outputStream)
}
} finally {
outputStream.closeEntry()
} }
} }
} }
/**
* Represents a single resource to be added to a CorDapp JAR.
*/
internal sealed class JarEntryInfo(val fullyQualifiedName: String, val url: URL) {
abstract val entryName: String
/**
* Represents a class to be added to a CorDapp JAR.
*/
class ClassJarEntryInfo(val clazz: Class<*>) : JarEntryInfo(clazz.name, clazz.classFileURL()) {
override val entryName = "${fullyQualifiedName.packageToJarPath()}$fileExtensionSeparator$classFileExtension"
}
/**
* Represents a resource file to be added to a CorDapp JAR.
*/
class ResourceJarEntryInfo(fullyQualifiedName: String, url: URL) : JarEntryInfo(fullyQualifiedName, url) {
override val entryName: String
get() {
val extensionIndex = fullyQualifiedName.lastIndexOf(fileExtensionSeparator)
return "${fullyQualifiedName.substring(0 until extensionIndex).packageToJarPath()}${fullyQualifiedName.substring(extensionIndex)}"
}
}
operator fun component1(): String = fullyQualifiedName
operator fun component2(): URL = url
private companion object {
private const val classFileExtension = "class"
private const val fileExtensionSeparator = "."
private const val whitespace = " "
private const val whitespaceReplacement = "%20"
private fun Class<*>.classFileURL(): URL {
require(protectionDomain?.codeSource?.location != null) { "Invalid class $name for test CorDapp. Classes without protection domain cannot be referenced. This typically happens for Java / Kotlin types." }
return URI.create("${protectionDomain.codeSource.location}/${name.packageToJarPath()}$fileExtensionSeparator$classFileExtension".escaped()).toURL()
}
private fun String.escaped(): String = this.replace(whitespace, whitespaceReplacement)
}
} }

View File

@ -0,0 +1,93 @@
package net.corda.testing.node.internal
import net.corda.core.internal.inputStream
import net.corda.node.internal.cordapp.get
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import java.util.jar.JarInputStream
class TestCordappsUtilsTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Test
fun `test simplifyScanPackages`() {
assertThat(simplifyScanPackages(emptyList())).isEmpty()
assertThat(simplifyScanPackages(listOf("com.foo.bar"))).containsExactlyInAnyOrder("com.foo.bar")
assertThat(simplifyScanPackages(listOf("com.foo", "com.foo"))).containsExactlyInAnyOrder("com.foo")
assertThat(simplifyScanPackages(listOf("com.foo", "com.bar"))).containsExactlyInAnyOrder("com.foo", "com.bar")
assertThat(simplifyScanPackages(listOf("com.foo", "com.foo.bar"))).containsExactlyInAnyOrder("com.foo")
assertThat(simplifyScanPackages(listOf("com.foo.bar", "com.foo"))).containsExactlyInAnyOrder("com.foo")
assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo.bar"))).containsExactlyInAnyOrder("com.foobar", "com.foo.bar")
assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo"))).containsExactlyInAnyOrder("com.foobar", "com.foo")
}
@Test
fun `packageAsJar writes out the CorDapp info into the manifest`() {
val cordapp = cordappForPackages("net.corda.testing.node.internal")
.withTargetVersion(123)
.withName("TestCordappsUtilsTest")
val jarFile = packageAsJar(cordapp)
JarInputStream(jarFile.inputStream()).use {
assertThat(it.manifest["Target-Platform-Version"]).isEqualTo("123")
assertThat(it.manifest["Name"]).isEqualTo("TestCordappsUtilsTest")
}
}
@Test
fun `packageAsJar on leaf package`() {
val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node.internal"))
assertThat(entries).contains(
"net/corda/testing/node/internal/TestCordappsUtilsTest.class",
"net/corda/testing/node/internal/resource.txt" // Make sure non-class resource files are also picked up
).doesNotContain(
"net/corda/testing/node/MockNetworkTest.class"
)
// Make sure the MockNetworkTest class does actually exist to ensure the above is not a false-positive
assertThat(javaClass.classLoader.getResource("net/corda/testing/node/MockNetworkTest.class")).isNotNull()
}
@Test
fun `packageAsJar on package with sub-packages`() {
val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node"))
assertThat(entries).contains(
"net/corda/testing/node/internal/TestCordappsUtilsTest.class",
"net/corda/testing/node/internal/resource.txt",
"net/corda/testing/node/MockNetworkTest.class"
)
}
@Test
fun `packageAsJar on single class`() {
val entries = packageAsJarThenReadBack(cordappForClasses(InternalMockNetwork::class.java))
assertThat(entries).containsOnly("${InternalMockNetwork::class.java.name.replace('.', '/')}.class")
}
private fun packageAsJar(cordapp: TestCordappImpl): Path {
val jarFile = tempFolder.newFile().toPath()
cordapp.packageAsJar(jarFile)
return jarFile
}
private fun packageAsJarThenReadBack(cordapp: TestCordappImpl): List<String> {
val jarFile = packageAsJar(cordapp)
val entries = ArrayList<String>()
JarInputStream(jarFile.inputStream()).use {
while (true) {
val e = it.nextJarEntry ?: break
entries += e.name
it.closeEntry()
}
}
return entries
}
}