Add DJVM to contract verification path inside Corda Node.

This commit is contained in:
Chris Rankin 2019-07-29 11:21:10 +01:00
parent 58f7fb9d4e
commit d4da84cc05
12 changed files with 165 additions and 8 deletions

View File

@ -75,6 +75,7 @@ buildscript {
ext.disruptor_version = constants.getProperty("disruptorVersion")
ext.metrics_version = constants.getProperty("metricsVersion")
ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion")
ext.djvm_version = constants.getProperty("djvmVersion")
ext.okhttp_version = '3.14.2'
ext.netty_version = '4.1.22.Final'
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")

View File

@ -30,6 +30,7 @@ snakeYamlVersion=1.19
caffeineVersion=2.7.0
metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1
djvmVersion=5.0-SNAPSHOT
openSourceBranch=https://github.com/corda/corda/blob/master
openSourceSamplesBranch=https://github.com/corda/samples/blob/master
jolokiaAgentVersion=1.6.1

View File

@ -29,7 +29,7 @@ fun LedgerTransaction.prepareVerify(extraAttachments: List<Attachment>) = this.i
* Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the
* wrong object instance. This class helps avoid that.
*/
open class Verifier(val ltx: LedgerTransaction, protected open val transactionClassLoader: ClassLoader) {
open class Verifier(val ltx: LedgerTransaction, protected val transactionClassLoader: ClassLoader) : AutoCloseable {
private val inputStates: List<TransactionState<*>> = ltx.inputs.map { it.state }
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
@ -358,6 +358,11 @@ open class Verifier(val ltx: LedgerTransaction, protected open val transactionCl
throw e
}
}
/**
* Placeholder function so that the [Verifier] can release any resources.
*/
override fun close() {}
}
/**

View File

@ -140,7 +140,9 @@ private constructor(
*/
@Throws(TransactionVerificationException::class)
fun verify() {
internalPrepareVerify(emptyList()).verify()
internalPrepareVerify(emptyList()).use { v ->
v.verify()
}
}
/**

View File

@ -38,6 +38,10 @@ configurations {
slowIntegrationTestCompile.extendsFrom testCompile
slowIntegrationTestRuntimeOnly.extendsFrom testRuntimeOnly
jdkRt.resolutionStrategy {
cacheChangingModulesFor 0, 'seconds'
}
}
sourceSets {
@ -156,6 +160,10 @@ dependencies {
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
// Sandbox for deterministic contract verification
compile "net.corda:corda-djvm:$djvm_version"
jdkRt "net.corda:deterministic-rt:latest.integration"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
testImplementation "junit:junit:$junit_version"

View File

@ -8,6 +8,8 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda standalone node'
evaluationDependsOn(':node')
configurations {
runtimeArtifacts.extendsFrom runtimeClasspath
capsuleRuntime
@ -50,6 +52,12 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
from configurations.capsuleRuntime.files.collect { zipTree(it) }
with jar
into('djvm') {
from project(':node').configurations['jdkRt'].singleFile
rename 'deterministic-rt(.*)', 'deterministic-rt.jar'
fileMode = 0444
}
capsuleManifest {
applicationVersion = corda_release_version
applicationId = "net.corda.node.Corda"

View File

@ -6,12 +6,21 @@ import com.typesafe.config.*;
import sun.misc.Signal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
public class CordaCaplet extends Capsule {
private static final String DETERMINISTIC_RT = "deterministic-rt.jar";
private static final String DJVM_DIR ="djvm";
private static final String DETERMINISTIC_RT_RESOURCE = "/" + DJVM_DIR + "/" + DETERMINISTIC_RT;
private Config nodeConfig = null;
private String baseDir = null;
@ -79,10 +88,57 @@ public class CordaCaplet extends Capsule {
return null;
}
private void installDJVM() {
Path djvmDir = Paths.get(baseDir, DJVM_DIR);
if (!djvmDir.toFile().mkdir() && !Files.isDirectory(djvmDir)) {
log(LOG_VERBOSE, "DJVM directory could not be created");
} else {
Path deterministicRt = djvmDir.resolve(DETERMINISTIC_RT);
Path sourceRt = appDir().resolve(DJVM_DIR).resolve(DETERMINISTIC_RT);
if (Files.isRegularFile(sourceRt)) {
try {
// Forcibly reinstall the deterministic APIs.
Files.deleteIfExists(deterministicRt);
Files.createSymbolicLink(deterministicRt, sourceRt);
} catch (UnsupportedOperationException | IOException e) {
copyFile(sourceRt, deterministicRt);
}
} else {
URL rtURL = getClass().getResource(DETERMINISTIC_RT_RESOURCE);
if (rtURL == null) {
log(LOG_VERBOSE, DETERMINISTIC_RT_RESOURCE + " missing from Corda capsule");
} else {
copyResource(rtURL, deterministicRt);
}
}
}
}
private void copyFile(Path source, Path target) {
try {
Files.copy(source, target, REPLACE_EXISTING);
} catch (IOException e) {
//noinspection ResultOfMethodCallIgnored
target.toFile().delete();
log(LOG_VERBOSE, e);
}
}
private void copyResource(URL source, Path target) {
try (InputStream input = source.openStream()) {
Files.copy(input, target, REPLACE_EXISTING);
} catch (IOException e) {
//noinspection ResultOfMethodCallIgnored
target.toFile().delete();
log(LOG_VERBOSE, e);
}
}
@Override
protected ProcessBuilder prelaunch(List<String> jvmArgs, List<String> args) {
checkJavaVersion();
nodeConfig = parseConfigFile(args);
installDJVM();
return super.prelaunch(jvmArgs, args);
}

View File

@ -34,6 +34,9 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.djvm.source.ApiSource
import net.corda.djvm.source.BootstrapClassLoader
import net.corda.djvm.source.EmptyApi
import net.corda.node.CordaClock
import net.corda.node.VersionInfo
import net.corda.node.internal.classloading.requireAnnotation
@ -125,7 +128,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
protected val versionInfo: VersionInfo,
protected val flowManager: FlowManager,
val serverThread: AffinityExecutor.ServiceAffinityExecutor,
val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
val busyNodeLatch: ReusableLatch = ReusableLatch(),
bootstrapSource: ApiSource = EmptyApi) : SingletonSerializeAsToken() {
protected abstract val log: Logger
@Suppress("LeakingThis")
@ -191,7 +195,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage).also {
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage, bootstrapSource).also {
attachments.servicesForResolution = it
}
@Suppress("LeakingThis")

View File

@ -0,0 +1,38 @@
package net.corda.node.internal
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.ContractVerifier
import net.corda.core.internal.Verifier
import net.corda.core.transactions.LedgerTransaction
import net.corda.djvm.SandboxConfiguration
import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.execution.*
import net.corda.djvm.source.ClassSource
class DeterministicVerifier(
ltx: LedgerTransaction,
transactionClassLoader: ClassLoader,
private val analysisConfiguration: AnalysisConfiguration
) : Verifier(ltx, transactionClassLoader) {
override fun verifyContracts() {
try {
val configuration = SandboxConfiguration.of(
enableTracing = false,
analysisConfiguration = analysisConfiguration
)
val executor = SandboxRawExecutor(configuration)
executor.run(ClassSource.fromClassName(ContractVerifier::class.java.name), ltx)
} catch (e: Exception) {
throw DeterministicVerificationException(ltx.id, e.message ?: "", e)
}
}
override fun close() {
analysisConfiguration.close()
}
}
class DeterministicVerificationException(id: SecureHash, message: String, cause: Throwable)
: TransactionVerificationException(id, message, cause)

View File

@ -19,6 +19,7 @@ import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.div
import net.corda.core.internal.errors.AddressBindingException
import net.corda.core.internal.getJavaUpdateVersion
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.notary.NotaryService
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
@ -29,6 +30,9 @@ import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.djvm.source.ApiSource
import net.corda.djvm.source.BootstrapClassLoader
import net.corda.djvm.source.EmptyApi
import net.corda.node.CordaClock
import net.corda.node.SimpleClock
import net.corda.node.VersionInfo
@ -106,7 +110,8 @@ open class Node(configuration: NodeConfiguration,
versionInfo,
flowManager,
// Under normal (non-test execution) it will always be "1"
AffinityExecutor.ServiceAffinityExecutor("Node thread-${sameVmNodeCounter.incrementAndGet()}", 1)
AffinityExecutor.ServiceAffinityExecutor("Node thread-${sameVmNodeCounter.incrementAndGet()}", 1),
bootstrapSource = createBootstrapSource(configuration)
) {
override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): NodeInfo =
@ -171,6 +176,17 @@ open class Node(configuration: NodeConfiguration,
false
}
}
fun createBootstrapSource(config: NodeConfiguration): ApiSource {
val bootstrapSource = config.baseDirectory.resolve("djvm").resolve("deterministic-rt.jar")
return if (bootstrapSource.isRegularFile()) {
staticLog.info("Deterministic Runtime: {}", bootstrapSource)
BootstrapClassLoader(bootstrapSource)
} else {
staticLog.warn("NO DETERMINISTIC RUNTIME FOUND - will use host JVM instead.")
EmptyApi
}
}
}
override val log: Logger get() = staticLog

View File

@ -3,6 +3,7 @@ package net.corda.node.internal
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.internal.Verifier
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage
@ -14,13 +15,19 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.transactions.WireTransaction.Companion.resolveStateRefBinaryComponent
import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.analysis.Whitelist
import net.corda.djvm.source.ApiSource
import net.corda.djvm.source.UserPathSource
import java.net.URLClassLoader
data class ServicesForResolutionImpl(
override val identityService: IdentityService,
override val attachments: AttachmentStorage,
override val cordappProvider: CordappProvider,
override val networkParametersService: NetworkParametersService,
private val validatedTransactions: TransactionStorage
private val validatedTransactions: TransactionStorage,
private val djvmBootstrapSource: ApiSource
) : ServicesForResolution {
override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?:
throw IllegalArgumentException("No current parameters in network parameters storage")
@ -74,6 +81,16 @@ data class ServicesForResolutionImpl(
override fun specialise(ltx: LedgerTransaction): LedgerTransaction {
// Specialise the LedgerTransaction here so that
// contracts are verified inside the DJVM!
return ltx
return ltx.specialise { tx, cl ->
(cl as? URLClassLoader)?.run { DeterministicVerifier(tx, cl, createSandbox(cl)) } ?: Verifier(tx, cl)
}
}
private fun createSandbox(classLoader: URLClassLoader): AnalysisConfiguration {
return AnalysisConfiguration.createRoot(
userSource = UserPathSource(classLoader.urLs),
whitelist = Whitelist.MINIMAL,
bootstrapSource = djvmBootstrapSource
)
}
}

View File

@ -21,6 +21,7 @@ import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.djvm.source.EmptyApi
import net.corda.node.VersionInfo
import net.corda.node.internal.ServicesForResolutionImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader
@ -430,7 +431,7 @@ open class MockServices private constructor(
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(initialNetworkParameters)
protected val servicesForResolution: ServicesForResolution
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions)
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions, EmptyApi)
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService(clock, keyManagementService, servicesForResolution, database, schemaService, cordappLoader.appClassLoader).apply { start() }