mirror of
https://github.com/corda/corda.git
synced 2025-04-19 08:36:39 +00:00
Add DJVM to contract verification path inside Corda Node.
This commit is contained in:
parent
58f7fb9d4e
commit
d4da84cc05
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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() {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,7 +140,9 @@ private constructor(
|
||||
*/
|
||||
@Throws(TransactionVerificationException::class)
|
||||
fun verify() {
|
||||
internalPrepareVerify(emptyList()).verify()
|
||||
internalPrepareVerify(emptyList()).use { v ->
|
||||
v.verify()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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() }
|
||||
|
Loading…
x
Reference in New Issue
Block a user